@black-market/sdk 0.14.1 → 0.15.1
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 +9 -7
- package/dist/lifecycle/authors.js +2 -2
- package/dist/lifecycle/authors.js.map +1 -1
- package/dist/lifecycle/auto-compound.d.ts +21 -0
- package/dist/lifecycle/auto-compound.d.ts.map +1 -0
- package/dist/lifecycle/auto-compound.js +34 -0
- package/dist/lifecycle/auto-compound.js.map +1 -0
- package/dist/lifecycle/calibration.d.ts +2 -4
- package/dist/lifecycle/calibration.d.ts.map +1 -1
- package/dist/lifecycle/calibration.js +1 -26
- package/dist/lifecycle/calibration.js.map +1 -1
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts +73 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.d.ts.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js +75 -0
- package/dist/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.js.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts +73 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.d.ts.map +1 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js +75 -0
- package/dist/lifecycle/deployments/auto-compound-static-pool-v1-20261028.js.map +1 -0
- package/dist/lifecycle/gas-profile.d.ts +39 -0
- package/dist/lifecycle/gas-profile.d.ts.map +1 -0
- package/dist/lifecycle/gas-profile.js +193 -0
- package/dist/lifecycle/gas-profile.js.map +1 -0
- package/dist/lifecycle/index.d.ts +6 -4
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +6 -4
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +2 -0
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +30 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/opening-buys.d.ts +13 -0
- package/dist/lifecycle/opening-buys.d.ts.map +1 -0
- package/dist/lifecycle/opening-buys.js +171 -0
- package/dist/lifecycle/opening-buys.js.map +1 -0
- package/dist/lifecycle/planner.d.ts +6 -9
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +175 -265
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +10 -3
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +13 -1
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts +1 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +21 -20
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.d.ts +25 -30
- package/dist/lifecycle/rpc.d.ts.map +1 -1
- package/dist/lifecycle/rpc.js +63 -81
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +2 -4
- package/dist/lifecycle/snapshot.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +29 -6
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +62 -27
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +55 -122
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js +3 -3
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg.d.ts +1 -1
- package/dist/lifecycle/ufg.d.ts.map +1 -1
- package/dist/lifecycle/ufg.js +4 -8
- package/dist/lifecycle/ufg.js.map +1 -1
- package/docs/launch-lifecycle.md +110 -78
- package/docs/lifecycle-gas-profiles.md +90 -0
- package/docs/reference.md +99 -47
- package/examples/.env.example +0 -3
- package/examples/README.md +14 -11
- package/examples/launch-example-support.ts +4 -9
- package/examples/launch-example.ts +30 -36
- package/examples/launch-lifecycle-smoke.ts +11 -21
- package/examples/launch.ts +8 -11
- package/package.json +1 -1
- package/src/lifecycle/authors.ts +2 -2
- package/src/lifecycle/auto-compound.ts +38 -0
- package/src/lifecycle/calibration.ts +3 -23
- package/src/lifecycle/deployments/auto-compound-dynamic-pool-v1-20261028.ts +77 -0
- package/src/lifecycle/deployments/auto-compound-static-pool-v1-20261028.ts +77 -0
- package/src/lifecycle/gas-profile.ts +186 -0
- package/src/lifecycle/index.ts +6 -4
- package/src/lifecycle/markets.ts +25 -3
- package/src/lifecycle/opening-buys.ts +157 -0
- package/src/lifecycle/planner.ts +168 -247
- package/src/lifecycle/presets.ts +15 -1
- package/src/lifecycle/progress.ts +19 -20
- package/src/lifecycle/rpc.ts +58 -68
- package/src/lifecycle/snapshot.ts +2 -4
- package/src/lifecycle/transactions.ts +69 -27
- package/src/lifecycle/types.ts +60 -99
- package/src/lifecycle/ufg.ts +4 -8
- package/dist/lifecycle/fork.d.ts +0 -15
- package/dist/lifecycle/fork.d.ts.map +0 -1
- package/dist/lifecycle/fork.js +0 -36
- package/dist/lifecycle/fork.js.map +0 -1
- package/dist/lifecycle/simulation.d.ts +0 -18
- package/dist/lifecycle/simulation.d.ts.map +0 -1
- package/dist/lifecycle/simulation.js +0 -754
- package/dist/lifecycle/simulation.js.map +0 -1
- package/src/lifecycle/fork.ts +0 -36
- package/src/lifecycle/simulation.ts +0 -689
package/docs/reference.md
CHANGED
|
@@ -56,7 +56,7 @@ These are structural maxima, not a guarantee that activation fits gas.
|
|
|
56
56
|
| Initial executor bounty | 0–1,000 bps; UFG requires 0 |
|
|
57
57
|
| Gas-envelope caps | Positive uint64 bigint |
|
|
58
58
|
| Gas headroom | `headroomBps`: 0–10,000; default 1,500 |
|
|
59
|
-
| Robinhood simulation-used gas | `maxSimulationGas`:
|
|
59
|
+
| Robinhood simulation-used gas | `maxSimulationGas`: optional aggregate policy ceiling; no SDK-invented default |
|
|
60
60
|
|
|
61
61
|
Use `LIFECYCLE_MAX_ERC404_SUPPLY` and `LIFECYCLE_MAX_REWARD_ERC20_SUPPLY` for the
|
|
62
62
|
exported supply bounds. Market token budgets equal total minted supply; position
|
|
@@ -133,8 +133,42 @@ UFG adds a canonical dynamic tuple via `encodeUfgLaunchConfig`:
|
|
|
133
133
|
`abi.encode((uint16,uint16,address[]))`, including the leading `0x20` offset.
|
|
134
134
|
`decodeUfgLaunchConfig` rejects alternate encodings and extra bytes;
|
|
135
135
|
`hashUfgLaunchConfig` hashes the tuple. Config6 appends typed `ufgLaunchConfig`
|
|
136
|
-
for UFG profiles only; Abyss carries no UFG tail. Full plan hashes bind
|
|
137
|
-
|
|
136
|
+
for UFG profiles only; Abyss carries no UFG tail. Full plan hashes bind every
|
|
137
|
+
auxiliary tail. HMM/UFG constructor economics hash the zero-salt prefix; the
|
|
138
|
+
auto-compounding profiles below hash the zero-salt prefix **and** their tail.
|
|
139
|
+
|
|
140
|
+
## Auto-compounding profiles
|
|
141
|
+
|
|
142
|
+
| `KnownLifecycleProfile` | Profile ID |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `V4AutoCompoundStaticPool` | `0xdd53c2c686f822b2d17536ccf4f9db954b4976d1be80404bc74571253179fae0` |
|
|
145
|
+
| `V4AutoCompoundDynamicPool` | `0x46b81bfe9316fbaa90e930c6f1d5ad5e6ecfced2c886fd5520ca2bae11b92744` |
|
|
146
|
+
|
|
147
|
+
`AUTO_COMPOUND_STATIC_V1_PROFILE_ID` / `AUTO_COMPOUND_DYNAMIC_V1_PROFILE_ID`
|
|
148
|
+
export these identities. The presets freeze the admitted envelope, adapter,
|
|
149
|
+
registration, topology and exact hook creation code. The generator checks preserved
|
|
150
|
+
admission bytes and successful receipts against registry `profile`,
|
|
151
|
+
`profileTopology`, `profileEnvelope`, adapter metadata, module identity and runtime
|
|
152
|
+
hashes at Robinhood block **100,486,085**
|
|
153
|
+
(`0xa41849e3e83fb977ed2fbfc7ff71c639bdf1fc34489dc46878128c021fdb6c44`).
|
|
154
|
+
These are construction snapshots, not a claim of current admission.
|
|
155
|
+
The disabled `0x1e41…1d68` / `0x53dd…e0d7` profiles are not supported.
|
|
156
|
+
|
|
157
|
+
`AutoCompoundConfigV1` contains `compoundShareBps` (integer 0–10,000).
|
|
158
|
+
`encodeAutoCompoundTail` / `decodeAutoCompoundTail` encode exactly
|
|
159
|
+
`abi.encode(bytes32 version, uint16 compoundShareBps)` (64 bytes), where
|
|
160
|
+
`AUTO_COMPOUND_CONFIG_VERSION = keccak256("black-market.auto-compound.config.v1")`.
|
|
161
|
+
The decoder requires the exact version, length and canonical narrow-integer
|
|
162
|
+
padding. Config6's typed `autoCompoundConfig` selects this tail only for these
|
|
163
|
+
two profiles; HMM and UFG tails cannot be combined with it.
|
|
164
|
+
`validateAutoCompoundConfig`, the market codec and `validateV4LifecycleMarket`
|
|
165
|
+
reject invalid shares; the market boundaries also require zero LP/developer fees.
|
|
166
|
+
Both InputToken and QuoteOnly fee modes are supported.
|
|
167
|
+
|
|
168
|
+
`hashPoolBoundV4MarketCommitment` includes the entire canonical zero-salt config
|
|
169
|
+
for these profiles. Never mine against a prefix-only commitment or append a share
|
|
170
|
+
after mining. Use [the launch guide](launch-lifecycle.md#configure-auto-compounding)
|
|
171
|
+
for selection and gas behavior.
|
|
138
172
|
|
|
139
173
|
## Choose a fee tier
|
|
140
174
|
|
|
@@ -158,49 +192,44 @@ splits are separate values. The example's 3000/60 is not the venue's sole fee ti
|
|
|
158
192
|
|
|
159
193
|
## Account for Nitro compute and poster gas
|
|
160
194
|
|
|
161
|
-
On
|
|
162
|
-
`
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
`transactionGasCeiling`, when set, is
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
the
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
Suffix steps are marked `provisional: true` and have no transaction estimate:
|
|
188
|
-
their conservative complete budgets check sequence feasibility, bounded across
|
|
189
|
-
the pinned affordable remaining balance. `buildNextTransaction` never reuses
|
|
190
|
-
those budgets as estimates; it freshly quotes and proves only the next command.
|
|
195
|
+
On chain4663, `ROBINHOOD_NATIVE_TRANSACTION_GAS_LIMIT` and the deployment preset's
|
|
196
|
+
`nativeTransactionGasLimit` specify32M **total** gas for begin, prepare, activate,
|
|
197
|
+
atomic and cancel. This includes poster gas. Guard-only maximum-gas getters are
|
|
198
|
+
not read. Pinned ArbSys version selects capacity coverage/native capability, not
|
|
199
|
+
a gas size. Other chains retain their resolved generic transaction ceiling.
|
|
200
|
+
|
|
201
|
+
`transactionGasCeiling`, when set, is a caller complete-envelope restriction.
|
|
202
|
+
Below the native ceiling it refuses; it never shrinks a launch step. Approvals
|
|
203
|
+
omit gas/fees and leave preparation to the wallet. One `ArbGasInfo.getPricesInWei`
|
|
204
|
+
leg in the initial aggregate supplies L1 per-byte and L2 prices, with no per-tx
|
|
205
|
+
NodeInterface oracle. See [capacity and poster evidence](lifecycle-gas-profiles.md).
|
|
206
|
+
|
|
207
|
+
`computeLifecycleLaunchGasBounds({ plan, predictedToken, ufg? })` returns conservative
|
|
208
|
+
atomic/activation compute upper bounds, excluding poster, or undefined for unknown
|
|
209
|
+
coverage. These select modes, not gas limits. Atomic is a preference that may select
|
|
210
|
+
staged; persist returned `planned.mode`. A large upper bound is uncertain, not
|
|
211
|
+
proof of overflow. Unknown activation gets full-sequence execution before begin.
|
|
212
|
+
Conversion-carrying commands/consuming activation always retain exact execution.
|
|
213
|
+
|
|
214
|
+
Only an immediate launch command receives a final gas/price/estimate/admission.
|
|
215
|
+
Suffix execution metrics stay provisional; build-next constructs only the canonical
|
|
216
|
+
next command at the same native ceiling with fresh fees and required admission.
|
|
217
|
+
There are no sizing passes or funds-only retries. Balance must cover value plus
|
|
218
|
+
the entire ceiling fee even when actual execution uses less gas. Incomplete proof
|
|
219
|
+
cannot authorize begin; explicit aggregate policies still refuse excessive work.
|
|
191
220
|
|
|
192
221
|
On Nitro, `estimate.posterGas` / `posterFee` are buffered allocations **inside**
|
|
193
222
|
`gasLimit`; `dataFeeIncludedInGas` is true, `dataFee` is zero, and `totalFee` equals
|
|
194
223
|
`gasLimit × gasPrice`. Do not add poster fees a second time. Custom `estimateDataFee`
|
|
195
224
|
is rejected on Nitro; generic EVM clients can use it for an external fee.
|
|
196
|
-
`feeConfidence` distinguishes
|
|
197
|
-
|
|
198
|
-
|
|
225
|
+
`feeConfidence` distinguishes actual execution evidence from the conditional
|
|
226
|
+
`native-ceiling-and-data` budget. Fresh gas price is max(pinned price, twice base fee).
|
|
227
|
+
Held Nitro price must meet max(ArbGasInfo L2 price, header base fee), without an
|
|
228
|
+
automatic bump. Reviewed gas differing from the ceiling is stale and refuses.
|
|
199
229
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
fixed transaction fees or proof that every permitted topology fits.
|
|
230
|
+
Capacity-only profiles account for route, position and HMM forwarding overhead.
|
|
231
|
+
Staged packing retains conservative per-market grouping, but never changes the
|
|
232
|
+
gas assigned to a launch transaction or splits its final activation.
|
|
204
233
|
|
|
205
234
|
## Cache static reads and inspect timing
|
|
206
235
|
|
|
@@ -210,19 +239,22 @@ fixed transaction fees or proof that every permitted topology fits.
|
|
|
210
239
|
| `sessionCache: createLifecycleSessionCache()` | Caller-owned reuse of SDK-selected static reads at one pin; successful deployment/backend observations also survive across blocks. Without it, those observations belong to the supplied client object. |
|
|
211
240
|
| `onDiagnostic(event)` | Local opt-in stage/RPC timing listener; no automatic console output or telemetry. |
|
|
212
241
|
|
|
213
|
-
Call `sessionCache.invalidate()`
|
|
214
|
-
|
|
215
|
-
keyed by chain
|
|
216
|
-
|
|
242
|
+
Call `sessionCache.invalidate()` after confirmed transactions to discard pinned
|
|
243
|
+
static values. Deployment/backend facts are retained for the session owner and
|
|
244
|
+
keyed by chain/account where applicable. Chain identity is cached by bound client
|
|
245
|
+
object; create a new client on a chain change rather than mutating/reusing it.
|
|
246
|
+
`sessionCache.block` is readonly `{ number, hash }` or undefined.
|
|
217
247
|
Planning pins retain the actual RPC L2 block number and header hash. EVM
|
|
218
248
|
`BLOCKNUMBER`/`BLOCKHASH`, including Multicall3's metadata getters, expose L1 values
|
|
219
249
|
on Nitro and are not L2 pin sources.
|
|
220
|
-
Balances, allowances, current progress, receipts,
|
|
221
|
-
|
|
250
|
+
Balances, allowances, current progress, receipts, policy callbacks and required
|
|
251
|
+
execution proof stay fresh. Planning/build-next do not read account queue nonces;
|
|
252
|
+
the wallet assigns them and retains the observed submitted nonce for recovery.
|
|
253
|
+
Session caching does not extend admission.
|
|
222
254
|
|
|
223
255
|
One RPC L2 header supplies number, hash, timestamp, gas limit and base fee.
|
|
224
256
|
The initial Multicall3 call uses that explicit L2 block and includes native balance,
|
|
225
|
-
funding/progress
|
|
257
|
+
funding/progress, Nitro getters and `getPricesInWei`. Independent getters are aggregated with no
|
|
226
258
|
minimum-count threshold. Multicall3 runtime code is attested once per session.
|
|
227
259
|
The 4663 preset enables `launchLensAbi.snapshot` without caller configuration:
|
|
228
260
|
one call replaces dependent token/hook/market/funding reads inside that aggregate.
|
|
@@ -315,6 +347,26 @@ From the checkout root, `pnpm test` runs offline tests and `pnpm examples` perfo
|
|
|
315
347
|
read-only deployed discovery. The following checks require separate infrastructure;
|
|
316
348
|
run write-capable checks only with explicit permission on disposable services.
|
|
317
349
|
|
|
350
|
+
### Auto-compounding read-only checks
|
|
351
|
+
|
|
352
|
+
After `pnpm build`, compare both profiles and fee modes with the live collector
|
|
353
|
+
factory's metadata/decoder and the reference Python encoder (no signing or sends):
|
|
354
|
+
|
|
355
|
+
```sh
|
|
356
|
+
BLACK_MARKET_REFERENCE_ROOT=/path/to/black-market \
|
|
357
|
+
node --test test/integration/auto-compound.live.test.mjs
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The reference checkout must contain `scripts/auto_compound_config.py` and its
|
|
361
|
+
Python dependencies. Regenerate the frozen deployment modules offline with:
|
|
362
|
+
|
|
363
|
+
```sh
|
|
364
|
+
node --experimental-transform-types scripts/generate-auto-compound-presets.mjs
|
|
365
|
+
# Explicitly refresh readback from a new pinned L2 header plus preserved admission evidence:
|
|
366
|
+
node --experimental-transform-types scripts/generate-auto-compound-presets.mjs \
|
|
367
|
+
--readback /path/to/black-market
|
|
368
|
+
```
|
|
369
|
+
|
|
318
370
|
### UFG owned-fork check
|
|
319
371
|
|
|
320
372
|
Run Anvil and the test in separate terminals; stop Anvil afterward:
|
package/examples/.env.example
CHANGED
|
@@ -37,6 +37,3 @@ NFT_BASE_URI=
|
|
|
37
37
|
# EXAMPLE_MAX_CALLDATA_BYTES=
|
|
38
38
|
# EXAMPLE_HEADROOM_BPS=1500
|
|
39
39
|
#
|
|
40
|
-
# Robinhood aggregate simulation gas-used budget defaults to 50M. Override only for
|
|
41
|
-
# a provider supporting that budget; buy-output calibration needs activation coverage.
|
|
42
|
-
# EXAMPLE_SIMULATION_GAS_CAP=100000000
|
package/examples/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Run the examples
|
|
2
2
|
|
|
3
|
-
These commands use the checkout's built `@black-market/sdk` **0.
|
|
3
|
+
These commands use the checkout's built `@black-market/sdk` **0.15.1** and viem.
|
|
4
4
|
Use **Node 24+** for direct TypeScript execution. Start with the one-market
|
|
5
|
-
`launch-erc20-abyss.ts`; larger cases
|
|
5
|
+
`launch-erc20-abyss.ts`; larger cases require staged preparation and a bounded activation.
|
|
6
6
|
For valuation, token ordering, ranges, and buy protection, read the
|
|
7
7
|
[launch guide](../docs/launch-lifecycle.md#price-the-launch).
|
|
8
8
|
|
|
@@ -44,7 +44,6 @@ Copy `launch-example.ts`, `launch-example-support.ts`, and `launch-example-kyber
|
|
|
44
44
|
| `EXAMPLE_ACCOUNT_GAS_CAP` | Optional wallet/account transaction gas-envelope ceiling. |
|
|
45
45
|
| `EXAMPLE_MAX_CALLDATA_BYTES` | Optional whole-transaction byte ceiling. |
|
|
46
46
|
| `EXAMPLE_HEADROOM_BPS` | Gas headroom, default `1500`; accepted range 0–10,000. |
|
|
47
|
-
| `EXAMPLE_SIMULATION_GAS_CAP` | Aggregate simulation-used gas budget; default 50M on chain 4663. |
|
|
48
47
|
|
|
49
48
|
Configure **exactly one** of `PRIVATE_KEY` and `EXAMPLE_KEYSTORE_PATH`.
|
|
50
49
|
A keystore must be a regular file owned by your user with owner-only permissions.
|
|
@@ -117,18 +116,20 @@ node examples/launch-erc20-abyss.ts --resume "launch-results/<original-run-dir>"
|
|
|
117
116
|
|
|
118
117
|
| Mode | Reads / writes | Identity |
|
|
119
118
|
| --- | --- | --- |
|
|
120
|
-
| No flags | Live discovery, funding quotes, preparation,
|
|
119
|
+
| No flags | Live discovery, funding quotes, preparation, local economic review and gas-budget checks; unsigned, no API writes | Fresh plan/run directory |
|
|
121
120
|
| `--broadcast` | Signs, spends, stages/uploads metadata, deploys, verifies, and publishes; no further prompt | **Fresh plan**, not the preceding dry run |
|
|
122
121
|
| `--resume <original-run-dir>` | May sign remaining steps or finish publication | Retained plan, receipts, mode, confirmation policy, and API session |
|
|
123
122
|
|
|
124
|
-
Dry-run needs the signer source, RPC/API, and sufficient funds for
|
|
123
|
+
Dry-run needs the signer source, RPC/API, and sufficient funds for current input/gas-budget checks.
|
|
125
124
|
Review its economics, then review the fresh broadcast output too. Mode flags cannot
|
|
126
125
|
be combined or duplicated. Resume is an execution authorization; omit `--broadcast`.
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
Every launch transaction uses the native ceiling,32M total on4663. Capacity profiles
|
|
128
|
+
only choose mode/chunks and reject oversized activation; exact local ordered math
|
|
129
|
+
protects opening buys. Multiple markets or conversion funding always stage.
|
|
130
|
+
There is no launch simulation, including Kyber. Routers enforce conversion minima
|
|
131
|
+
and deadlines on-chain; `assessment.ready` is not a receipt or guaranteed execution.
|
|
132
|
+
See [coverage and migration](../docs/lifecycle-gas-profiles.md).
|
|
132
133
|
|
|
133
134
|
### Fund opening buys
|
|
134
135
|
|
|
@@ -166,8 +167,10 @@ It does not automatically retry sends, adopt replacements, cancel, or create ano
|
|
|
166
167
|
| Unstarted / Cancelled / unresolved signed hash | Stops for reconciliation; do not use a new invocation as a retry. |
|
|
167
168
|
|
|
168
169
|
Resume retains salts and buy minima; it does not rerun preparation or recalibrate economics.
|
|
169
|
-
For replacements, retain original `transactionHash
|
|
170
|
-
|
|
170
|
+
For replacements, retain original `transactionHash`, effective `replacementHash`,
|
|
171
|
+
observed block number/hash and the actual submitted nonce as an exact bigint
|
|
172
|
+
(decimal string in journals). SDK planning has no account-queue nonce gate; the
|
|
173
|
+
wallet assigns and tracks nonces. A missing receipt alone does not establish a reorg.
|
|
171
174
|
|
|
172
175
|
Metadata is staged before deployment. Publication uses the **original activation hash**.
|
|
173
176
|
A `LaunchPublishPending` response means indexing is pending; the runner polls for up
|
|
@@ -252,14 +252,10 @@ export function exampleError(code, message, details = {}) {
|
|
|
252
252
|
return Object.assign(new Error(message), { name: "LaunchExampleError", code, ...details });
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
/**
|
|
256
|
-
export function rpcRequestSignal(signal
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// Cold nested-fork state fetches can exceed 30 seconds. Bound each request,
|
|
260
|
-
// including instrumented controlled simulation; never retry a source broadcast.
|
|
261
|
-
const timeout = AbortSignal.timeout(120_000);
|
|
262
|
-
return cleanup ? timeout : AbortSignal.any([signal, timeout]);
|
|
255
|
+
/** Bound each request and preserve interruption; launch planning owns no fork cleanup. */
|
|
256
|
+
export function rpcRequestSignal(signal) {
|
|
257
|
+
signal.throwIfAborted();
|
|
258
|
+
return AbortSignal.any([signal, AbortSignal.timeout(120_000)]);
|
|
263
259
|
}
|
|
264
260
|
|
|
265
261
|
|
|
@@ -540,7 +536,6 @@ export async function readExampleConfiguration(sdk, redactor, env = process.env)
|
|
|
540
536
|
chainTransactionGasLimit: gasCap("EXAMPLE_CHAIN_GAS_CAP"),
|
|
541
537
|
rpcTransactionGasLimit: gasCap("EXAMPLE_RPC_GAS_CAP"),
|
|
542
538
|
accountTransactionGasLimit: gasCap("EXAMPLE_ACCOUNT_GAS_CAP"),
|
|
543
|
-
rpcTotalSimulationGasLimit: gasCap("EXAMPLE_SIMULATION_GAS_CAP"),
|
|
544
539
|
maxCalldataBytes: integer("EXAMPLE_MAX_CALLDATA_BYTES", undefined, 1, Number.MAX_SAFE_INTEGER),
|
|
545
540
|
headroomBps: integer("EXAMPLE_HEADROOM_BPS", 1500, 0, 10000),
|
|
546
541
|
provenance: { scope: "user-ceilings", note: "Optional local tightening restrictions, not verified provider or account limits" },
|
|
@@ -78,7 +78,7 @@ function rpcClient(url, artifacts, signal, label) {
|
|
|
78
78
|
// the example RPC client is admitted identically to the UI it validates.
|
|
79
79
|
if (endpoint.hostname === "rpc.mainnet.chain.robinhood.com") headers.Origin = "https://abyss.trading";
|
|
80
80
|
return { async request({ method, params = [] }) {
|
|
81
|
-
const requestSignal = rpcRequestSignal(signal
|
|
81
|
+
const requestSignal = rpcRequestSignal(signal);
|
|
82
82
|
const started = Date.now();
|
|
83
83
|
try {
|
|
84
84
|
const response = await fetch(endpoint, {
|
|
@@ -90,7 +90,6 @@ function rpcClient(url, artifacts, signal, label) {
|
|
|
90
90
|
const body = await response.json();
|
|
91
91
|
if (body.error) throw exampleError(body.error.code ?? "RPC_ERROR", `${label} ${method}: ${body.error.message}`, { data: body.error.data, cause: body.error });
|
|
92
92
|
if (!("result" in body)) throw exampleError("INVALID_RPC_RESPONSE", `${label} ${method}: result was omitted`, { data: body });
|
|
93
|
-
if (method === "eth_simulateV1") artifacts.event("rpc-observation", { backend: label, method, params, result: body.result, elapsedMs: Date.now() - started });
|
|
94
93
|
return body.result;
|
|
95
94
|
} catch (error) {
|
|
96
95
|
artifacts.event("rpc-error", { backend: label, method, params, elapsedMs: Date.now() - started, error: errorEvidence(error) });
|
|
@@ -307,17 +306,13 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
307
306
|
market.retainedSupplyOpeningValuationUsdX18 = ratio.numerator * retainedSupply * market.quoteUsdX18 /
|
|
308
307
|
(ratio.denominator * 10n ** BigInt(market.quoteDecimals));
|
|
309
308
|
}
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const simulatedBuys = planned.plan.buys.map((buy, index) => {
|
|
318
|
-
const tokenOut = simulatedReceipt.tokenOut[index];
|
|
319
|
-
const quoteSpent = simulatedReceipt.quoteSpent[index];
|
|
320
|
-
assert.ok(tokenOut >= buy.minTokenOut && tokenOut > 0n && quoteSpent <= buy.quoteAmountIn, "Exact protected simulated buy");
|
|
309
|
+
const orderedOutputs = planned.assessment.gasProfile?.initialBuys;
|
|
310
|
+
if (!orderedOutputs) throw exampleError("MISSING_ECONOMIC_OUTPUTS", "Economic review requires the supported exact local opening curve");
|
|
311
|
+
const buyEvidence = "local-exact-curve";
|
|
312
|
+
assert.equal(orderedOutputs.length, planned.plan.buys.length, "Economic review ordered outputs");
|
|
313
|
+
const reviewedBuys = planned.plan.buys.map((buy, index) => {
|
|
314
|
+
const { tokenOut, quoteSpent } = orderedOutputs[index];
|
|
315
|
+
assert.ok(tokenOut >= buy.minTokenOut && tokenOut > 0n && quoteSpent <= buy.quoteAmountIn, "Exact protected opening buy");
|
|
321
316
|
const market = marketEconomics[buy.marketIndex];
|
|
322
317
|
const ratio = market.rawQuotePerRawToken;
|
|
323
318
|
return { buyIndex: index, marketIndex: buy.marketIndex, quoteAsset: market.quoteAsset, quoteSymbol: market.quoteSymbol, quoteSpent, tokenOut,
|
|
@@ -325,10 +320,10 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
325
320
|
feeAndPriceImpactPremiumBps: (quoteSpent * ratio.denominator - tokenOut * ratio.numerator) * 10000n /
|
|
326
321
|
(tokenOut * ratio.numerator),
|
|
327
322
|
// Effective execution price includes fees and path impact, not post-buy marginal spot.
|
|
328
|
-
priceBasis:
|
|
323
|
+
priceBasis: `${buyEvidence}-all-in-average-including-fees-and-price-impact` };
|
|
329
324
|
});
|
|
330
325
|
const economicReview = {
|
|
331
|
-
schema: "black-market.launch-economic-review.
|
|
326
|
+
schema: "black-market.launch-economic-review.v2", caseId: scenario.id, chainId: fixture.chainId,
|
|
332
327
|
targetMarketCapUsdX18: fixture.targetMarketCapUsdX18, quotes, metadata, image: image?.descriptor ?? null,
|
|
333
328
|
actualMintedSupply: token.supply, nftUnit: token.nftUnit, tokenDecimals: 18,
|
|
334
329
|
predictedToken, markets: marketEconomics,
|
|
@@ -337,8 +332,8 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
337
332
|
executorFeeBps: plan.executorFeeBps, feeAssets, v4Markets: scenario.markets.flatMap((spec, marketIndex) => spec.venue === "v4" ?
|
|
338
333
|
[{ marketIndex, feeMode: spec.feeMode ?? 0, protocolFeeDenominator: selected.v4.envelope.protocolFeeDenominator,
|
|
339
334
|
treasury: selected.v4.envelope.protocolTreasury, developerBeneficiary: selected.v4.envelope.beneficiary, developerFeeBps: 0 }] : []) },
|
|
340
|
-
initialBuy: { slippageBps: BUY_SLIPPAGE_BPS, funding: planned.plan.funding, orderedBuys: planned.plan.buys,
|
|
341
|
-
observedBlockNumber: planned.
|
|
335
|
+
initialBuy: { slippageBps: BUY_SLIPPAGE_BPS, funding: planned.plan.funding, orderedBuys: planned.plan.buys, reviewedBuys, evidence: buyEvidence,
|
|
336
|
+
observedBlockNumber: planned.assessment.blockNumber, observedBlockHash: planned.assessment.blockHash },
|
|
342
337
|
planHash: planned.planHash, finalizedPlan: finalizedPlan,
|
|
343
338
|
};
|
|
344
339
|
const reviewHash = economicReviewHash(economicReview);
|
|
@@ -346,7 +341,7 @@ async function constructPlan({ client, fixture, scenario, sdk, artifacts, signal
|
|
|
346
341
|
// Complete exact intent is owner-only and durable before attribution or chain signing.
|
|
347
342
|
artifacts.privateRecovery({
|
|
348
343
|
schema: "black-market.launch-example-recovery.v1", sdk: "node", caseId: artifacts.result.caseId,
|
|
349
|
-
plan: finalizedPlan, mode:
|
|
344
|
+
plan: finalizedPlan, mode: planned.mode, confirmations, executionLimits: fixture.executionLimits, economicReview: { ...economicReview, reviewHash },
|
|
350
345
|
});
|
|
351
346
|
artifacts.save("plan.json", finalizedPlan);
|
|
352
347
|
artifacts.save("hook-deployments.json", planned.hookDeployments);
|
|
@@ -477,7 +472,6 @@ function limitSource(fixture, artifacts) {
|
|
|
477
472
|
chainGasLimit: policy.chainTransactionGasLimit === undefined ? undefined : BigInt(policy.chainTransactionGasLimit),
|
|
478
473
|
rpcGasLimit: policy.rpcTransactionGasLimit === undefined ? undefined : BigInt(policy.rpcTransactionGasLimit),
|
|
479
474
|
accountGasLimit: policy.accountTransactionGasLimit === undefined ? undefined : BigInt(policy.accountTransactionGasLimit),
|
|
480
|
-
maxSimulationGas: policy.rpcTotalSimulationGasLimit === undefined ? undefined : BigInt(policy.rpcTotalSimulationGasLimit),
|
|
481
475
|
maxCalldataBytes: policy.maxCalldataBytes, headroomBps: policy.headroomBps,
|
|
482
476
|
};
|
|
483
477
|
artifacts.event("execution-limits", { limits, provenance: policy.provenance });
|
|
@@ -490,11 +484,11 @@ async function executePlan({ client, fixture, planned, sdk, artifacts, signal, l
|
|
|
490
484
|
let activation;
|
|
491
485
|
for (let step = 0; step < 64; step += 1) {
|
|
492
486
|
artifacts.stage("build-next", { step });
|
|
493
|
-
const
|
|
494
|
-
const
|
|
487
|
+
const stored = planned.transactions[step];
|
|
488
|
+
const reviewedTransaction = stored?.gas === undefined || stored.gasPrice === undefined ? undefined : stored;
|
|
489
|
+
const next = await sdk.buildNextTransaction({ client, planned, receipts: references, limits, reviewedTransaction, signal });
|
|
495
490
|
if (!next) return { references, activation };
|
|
496
|
-
assert.
|
|
497
|
-
for (const field of ["id", "kind", "chainId", "from", "to", "data", "value", "nonce", "gas", "gasPrice"]) assert.equal(next[field], reviewedTransaction[field], `Fresh ${field} must match the saved review`);
|
|
491
|
+
if (reviewedTransaction) for (const field of ["id", "kind", "chainId", "from", "to", "data", "value", "gas", "gasPrice"]) assert.equal(next[field], reviewedTransaction[field], `Fresh ${field} must match the saved exact envelope`);
|
|
498
492
|
if (retainedReferences.length) {
|
|
499
493
|
const progress = await sdk.readLaunchProgress({ client, planned, receipts: references, confirmations: planned.confirmations, signal });
|
|
500
494
|
assertRetainedExampleReceipts({ progress, references, artifacts });
|
|
@@ -509,12 +503,10 @@ async function executePlan({ client, fixture, planned, sdk, artifacts, signal, l
|
|
|
509
503
|
assert.ok(functionName, "Native-input conversion cases do not permit unrelated approval/cancellation writes");
|
|
510
504
|
const args = next.kind === "begin" ? [planned.plan, sdk.LifecycleMode.Staged] : next.kind === "prepare" ? [planned.plan, next.marketStart, next.marketCount] : [planned.plan];
|
|
511
505
|
assert.equal(next.data.toLowerCase(), encodeFunctionData({ abi: sdk.launchLifecycleAbi, functionName, args }).toLowerCase(), "Exact finalized-plan calldata");
|
|
512
|
-
assert.ok(next.gas !== undefined && (next.gasPrice !== undefined || next.maxFeePerGas !== undefined && next.maxPriorityFeePerGas !== undefined) && next.
|
|
513
|
-
|
|
514
|
-
assert.
|
|
515
|
-
const
|
|
516
|
-
assert.ok(next.gas > 0n && (ceiling === undefined || next.gas <= ceiling), "Complete gas envelope must fit supplied restrictions; Nitro compute is separately proved");
|
|
517
|
-
const calldataCap = next.admission.limits.maxCalldataBytes;
|
|
506
|
+
assert.ok(next.gas !== undefined && (next.gasPrice !== undefined || next.maxFeePerGas !== undefined && next.maxPriorityFeePerGas !== undefined) && next.assessment?.ready === true, "Actual next transaction needs a current gas budget");
|
|
507
|
+
const ceiling = next.assessment.limits.transactionGasCeiling;
|
|
508
|
+
assert.ok(next.gas === next.assessment.limits.executionGasCeiling && (ceiling === undefined || next.gas <= ceiling), "Launch gas must equal the native ceiling and fit supplied restrictions");
|
|
509
|
+
const calldataCap = next.assessment.limits.maxCalldataBytes;
|
|
518
510
|
assert.ok(calldataCap === undefined || (next.data.length - 2) / 2 <= calldataCap, "Exact calldata must fit any supplied cap");
|
|
519
511
|
const envelope = transactionEnvelope(next);
|
|
520
512
|
const wire = { ...envelope };
|
|
@@ -549,7 +541,7 @@ async function executePlan({ client, fixture, planned, sdk, artifacts, signal, l
|
|
|
549
541
|
addressEqual(includedBlock?.hash, receipt.blockHash, "Canonical included block");
|
|
550
542
|
const latest = BigInt(await client.request({ method: "eth_blockNumber" }));
|
|
551
543
|
assert.ok(latest >= BigInt(receipt.blockNumber) + BigInt(planned.confirmations) - 1n, "Required confirmations must be included canonically");
|
|
552
|
-
const reference = { transactionHash: row.transactionHash, observedBlockNumber: BigInt(receipt.blockNumber), observedBlockHash: receipt.blockHash, confirmations: planned.confirmations };
|
|
544
|
+
const reference = { transactionHash: row.transactionHash, nonce: BigInt(row.transaction.nonce), observedBlockNumber: BigInt(receipt.blockNumber), observedBlockHash: receipt.blockHash, confirmations: planned.confirmations };
|
|
553
545
|
row.status = "confirmed"; row.reference = reference; artifacts.saveReceipts();
|
|
554
546
|
references.push(reference);
|
|
555
547
|
if (["atomic", "activate"].includes(next.kind)) {
|
|
@@ -882,9 +874,10 @@ async function executeExample({ fixture, privateKey, scenario, sdk, artifacts, s
|
|
|
882
874
|
const { planned, predictedToken, economicReview, reviewHash } = await constructPlan({ client, fixture, scenario, sdk, artifacts, signal, limits, metadata, image });
|
|
883
875
|
const { plan } = planned;
|
|
884
876
|
artifacts.save("planning.json", planned);
|
|
885
|
-
artifacts.event("execution-plan", { mode: planned.mode,
|
|
886
|
-
|
|
887
|
-
|
|
877
|
+
artifacts.event("execution-plan", { mode: planned.mode, assessment: planned.assessment, prerequisites: planned.prerequisites, transactions: planned.transactions });
|
|
878
|
+
if (scenario.mode === "staged") assert.equal(planned.mode, "staged", "Explicit staged mode is retained");
|
|
879
|
+
addressEqual(planned.predictedToken, predictedToken, "Planner token prediction");
|
|
880
|
+
if (!planned.assessment.ready) throw exampleError(planned.assessment.code ?? "PLAN_NOT_READY", planned.assessment.reason ?? "Refresh the launch review before continuing.", { assessment: planned.assessment });
|
|
888
881
|
const usd = (value) => `${value / UNIT}.${(value % UNIT).toString().padStart(18, "0")}`;
|
|
889
882
|
const fundingSummary = economicReview.quotes.every((quote) => quote.conversion.nativeWrap === true)
|
|
890
883
|
? "payable native wrap"
|
|
@@ -894,7 +887,7 @@ async function executeExample({ fixture, privateKey, scenario, sdk, artifacts, s
|
|
|
894
887
|
console.log(`Economic review ${reviewHash}\n${metadata.name} (${metadata.symbol}); target USD ${usd(economicReview.targetMarketCapUsdX18)}; minted supply ${usd(economicReview.actualMintedSupply)}\n` +
|
|
895
888
|
economicReview.markets.map((market) => `Market ${market.marketIndex} ${market.venue}/${market.quoteSymbol}: retained-supply opening valuation USD ${usd(market.retainedSupplyOpeningValuationUsdX18)} (minted-supply USD ${usd(market.openingValuationUsdX18)}), token USD ${usd(market.unitPriceUsdX18)}, token ${market.quoteSymbol} ${usd(market.unitPriceQuoteX18)}, ${market.positions.length} positions`).join("\n") +
|
|
896
889
|
`\nExpected rounding burn ${economicReview.expectedUnusedBurn} raw token units; ${planned.plan.buys.length} ordered buys, native input ${fixture.buyAmountWei} wei each (${fundingSummary}); 50 bps conversion and ordered-buy protection.\nLP fee 3000 pips; V4 hook max 10000/min 1000 pips (fixed maximum schedule); executor 275 bps.\nImage ${image ? `${image.descriptor.contentType}, ${image.descriptor.contentLength} bytes, ${image.descriptor.sha256}` : "none (metadata only)"}.\nReview allocations, fees and exact protected buys: ${artifacts.directory}/economic-review.json`);
|
|
897
|
-
console.log(economicReview.initialBuy.
|
|
890
|
+
console.log(economicReview.initialBuy.reviewedBuys.map((buy) => `Buy ${buy.buyIndex} market ${buy.marketIndex}/${buy.quoteSymbol}: quote spent ${buy.quoteSpent} raw units; ${economicReview.initialBuy.evidence} all-in USD/token ${usd(buy.allInUnitPriceUsdX18)}; fees + price-impact premium ${buy.feeAndPriceImpactPremiumBps} bps vs opening`).join("\n"));
|
|
898
891
|
for (const quote of economicReview.quotes) {
|
|
899
892
|
const seed = quote.usdSeed;
|
|
900
893
|
console.log(`${quote.symbol}/USD ${usd(seed.priceUsdX18)}; source ${seed.source}; chain ${seed.chainId}; quote ${seed.tokenAddress} (${seed.tokenDecimals} decimals); retrieved ${seed.retrievedAt}.\n` +
|
|
@@ -1054,7 +1047,8 @@ export async function resumeLaunchExample({ fixture, privateKey, sdk, artifacts,
|
|
|
1054
1047
|
assert.match(reference.transactionHash, /^0x[\da-fA-F]{64}$/, "Original receipt hash");
|
|
1055
1048
|
assert.match(reference.observedBlockHash, /^0x[\da-fA-F]{64}$/, "Original receipt block hash");
|
|
1056
1049
|
assert.ok(reference.observedBlockNumber !== undefined && reference.observedBlockNumber !== null, "Original receipt block number");
|
|
1057
|
-
|
|
1050
|
+
if (reference.nonce !== undefined) assert.ok(typeof reference.nonce === "bigint" || typeof reference.nonce === "string" && /^(0|[1-9]\d*)$/.test(reference.nonce), "Retained submitted nonce must be an exact unsigned integer");
|
|
1051
|
+
return { ...reference, ...(reference.nonce === undefined ? {} : { nonce: BigInt(reference.nonce) }), observedBlockNumber: BigInt(reference.observedBlockNumber), confirmations: reference.confirmations ?? confirmations };
|
|
1058
1052
|
});
|
|
1059
1053
|
const retainedHashes = new Set(references.map((reference) => reference.transactionHash.toLowerCase()));
|
|
1060
1054
|
if (references.some((reference) => reference.replacementHash && !same(reference.replacementHash, reference.transactionHash)) ||
|
|
@@ -1079,7 +1073,7 @@ export async function resumeLaunchExample({ fixture, privateKey, sdk, artifacts,
|
|
|
1079
1073
|
if (![sdk.LifecyclePhase.Preparing, sdk.LifecyclePhase.Ready, sdk.LifecyclePhase.Active].includes(progress.canonical.phase)) {
|
|
1080
1074
|
throw exampleError("RESUME_PHASE_UNSUPPORTED", "Resume requires canonical Preparing, Ready or Active; an unstarted or cancelled launch must never be relaunched");
|
|
1081
1075
|
}
|
|
1082
|
-
if (!planned.
|
|
1076
|
+
if (!planned.assessment.ready) throw exampleError(planned.assessment.code ?? "PLAN_NOT_READY", planned.assessment.reason ?? "Refresh the unfinished launch review before continuing.", { assessment: planned.assessment });
|
|
1083
1077
|
Object.assign(artifacts.result.chain, { planHash: planned.planHash, launchId: planned.launchId, predictedToken: planned.predictedToken });
|
|
1084
1078
|
// Canonical receipts restore the begin/deployment logs needed by verifyChain.
|
|
1085
1079
|
for (const reference of references) {
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
// Deliberately executing
|
|
1
|
+
// Deliberately executing actual transactions on an owned disposable loopback fixture only.
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { decodeEventLog, decodeFunctionResult, encodeFunctionData, toHex, zeroHash } from "viem";
|
|
5
5
|
import {
|
|
6
6
|
buildClaimDeveloperFeesPageTransaction, buildNextTransaction, buildPoolBoundHookDeploymentTransaction,
|
|
7
|
-
|
|
7
|
+
decodeDeveloperClaimReceipt, decodePoolBoundV4LifecycleMarketConfig,
|
|
8
8
|
encodeLaunchPlan, encodePoolBoundV4LifecycleMarketConfig, hashLaunchIdentity, hashLaunchPlan,
|
|
9
9
|
launchLifecycleAbi, lifecycleAdapterAbi, lifecycleDirectoryAbi, lifecycleErc20Abi, lifecycleFeeHubAbi,
|
|
10
10
|
LifecyclePhase, parseLaunchPlan, planLaunch, predictLifecycleToken, prepareAndPlanLifecycleLaunch,
|
|
11
11
|
readAuthorHubs, readDeveloperFees, readLaunchProgress, readLifecycleProfiles, readPoolBoundHookDeployment,
|
|
12
|
-
|
|
12
|
+
isPoolBoundV4ConfigVersion,
|
|
13
13
|
} from "@black-market/sdk/lifecycle";
|
|
14
14
|
const [manifestFile, plansFile] = process.argv.slice(2);
|
|
15
15
|
if (!manifestFile || !plansFile) throw new Error("Usage: node examples/launch-lifecycle-smoke.ts manifest.json sdk-plans.json");
|
|
16
16
|
const manifest = JSON.parse(await readFile(manifestFile, "utf8"));
|
|
17
17
|
const exported = JSON.parse(await readFile(plansFile, "utf8"));
|
|
18
18
|
const rpcUrl = process.env.LAUNCH_LIFECYCLE_RPC_URL;
|
|
19
|
-
|
|
20
|
-
if (!rpcUrl || !forkRpcUrl || !["127.0.0.1", "localhost", "[::1]"].includes(new URL(rpcUrl).hostname) || process.env.LAUNCH_LIFECYCLE_ALLOW_LOCAL_EXECUTION !== "1" || manifest.fixtureOnly !== true) throw new Error("Smoke requires explicitly authorized owned loopback fixture execution and a separate disposable fork");
|
|
19
|
+
if (!rpcUrl || !["127.0.0.1", "localhost", "[::1]"].includes(new URL(rpcUrl).hostname) || process.env.LAUNCH_LIFECYCLE_ALLOW_LOCAL_EXECUTION !== "1" || manifest.fixtureOnly !== true) throw new Error("Smoke requires explicitly authorized owned loopback fixture execution");
|
|
21
20
|
if (!Array.isArray(exported.plans) || exported.plans.length === 0) throw new Error("Current Solidity-exported reviewed plans are required");
|
|
22
21
|
let requestId = 0;
|
|
23
22
|
const client = { async request({ method, params = [] }) {
|
|
@@ -36,7 +35,6 @@ const limits = async ({ block, chainId, account, orchestrator }) => ({
|
|
|
36
35
|
chainGasLimit: BigInt(policy.chainTransactionGasLimit), rpcGasLimit: BigInt(policy.rpcTransactionGasLimit),
|
|
37
36
|
accountGasLimit: BigInt(policy.accountTransactionGasLimit), maxCalldataBytes: Number(policy.maxCalldataBytes), headroomBps: Number(policy.headroomBps),
|
|
38
37
|
});
|
|
39
|
-
const fork = createControlledLifecycleFork({ sourceRpcUrl: rpcUrl, forkRpcUrl, allowTransactions: true, impersonation: "anvil" });
|
|
40
38
|
async function readContract(to, abi, functionName, args = []) {
|
|
41
39
|
return decodeFunctionResult({ abi, functionName, data: await client.request({ method: "eth_call", params: [{ to, data: encodeFunctionData({ abi, functionName, args }) }, "latest"] }) });
|
|
42
40
|
}
|
|
@@ -46,7 +44,7 @@ async function send(transaction) {
|
|
|
46
44
|
const gasPrice = transaction.gasPrice ?? BigInt(await client.request({ method: "eth_gasPrice" }));
|
|
47
45
|
const head = await client.request({ method: "eth_getBlockByNumber", params: ["latest", false] });
|
|
48
46
|
const ceiling = [BigInt(head.gasLimit), BigInt(policy.chainTransactionGasLimit), BigInt(policy.rpcTransactionGasLimit), BigInt(policy.accountTransactionGasLimit)].reduce((a, b) => a < b ? a : b);
|
|
49
|
-
const wire = { from: transaction.from, to: transaction.to, data: transaction.data, value: toHex(transaction.value), gasPrice: toHex(gasPrice)
|
|
47
|
+
const wire = { from: transaction.from, to: transaction.to, data: transaction.data, value: toHex(transaction.value), gasPrice: toHex(gasPrice) };
|
|
50
48
|
const estimated = transaction.gas ?? ((BigInt(await client.request({ method: "eth_estimateGas", params: [{ ...wire, gas: toHex(ceiling) }] })) * BigInt(10000 + Number(policy.headroomBps)) + 9999n) / 10000n);
|
|
51
49
|
assert.ok(estimated <= ceiling, "Unsigned helper must still fit explicit local execution admission");
|
|
52
50
|
const transactionHash = await client.request({ method: "eth_sendTransaction", params: [{ ...wire, gas: toHex(estimated) }] });
|
|
@@ -81,7 +79,7 @@ for (const row of exported.plans) for (const mode of ["atomic", "staged"]) {
|
|
|
81
79
|
const draft = { ...plan, markets: plan.markets.map((market) => isPoolBoundV4ConfigVersion(market.configVersion) ? {
|
|
82
80
|
...market, config: encodePoolBoundV4LifecycleMarketConfig({ ...decodePoolBoundV4LifecycleMarketConfig(market.config, market.configVersion), hookSalt: zeroHash }),
|
|
83
81
|
} : market) };
|
|
84
|
-
let planned = await prepareAndPlanLifecycleLaunch({ client, account: draft.creator, plan: draft, mode, limits
|
|
82
|
+
let planned = await prepareAndPlanLifecycleLaunch({ client, account: draft.creator, plan: draft, mode, limits });
|
|
85
83
|
plan = planned.plan;
|
|
86
84
|
assert.equal(hashLaunchPlan(plan).toLowerCase(), row.planHash.toLowerCase(), "Offchain mining reproduces the independent constructor/salt vector");
|
|
87
85
|
if (mode === "staged") for (const deployment of planned.hookDeployments) {
|
|
@@ -90,22 +88,14 @@ for (const row of exported.plans) for (const mode of ["atomic", "staged"]) {
|
|
|
90
88
|
const actual = await readPoolBoundHookDeployment({ client, plan, marketIndex: deployment.marketIndex });
|
|
91
89
|
assert.equal(actual.predictedHook.toLowerCase(), deployment.predictedHook.toLowerCase());
|
|
92
90
|
}
|
|
93
|
-
if (mode === "staged" && planned.hookDeployments.length > 0) planned = await planLaunch({ client, account: plan.creator, plan, mode, limits
|
|
94
|
-
assert.equal(planned.
|
|
95
|
-
const atomicReceipt = planned.simulation.steps.find((step) => step.transactionId === "atomic")?.returnData;
|
|
96
|
-
if (atomicReceipt) {
|
|
97
|
-
const decoded = decodeFunctionResult({ abi: launchLifecycleAbi, functionName: "launchAtomic", data: atomicReceipt });
|
|
98
|
-
assert.equal(decoded.planHash.toLowerCase(), planned.planHash.toLowerCase());
|
|
99
|
-
for (let i = 0; i < plan.buys.length; ++i) assert.ok(decoded.tokenOut[i] >= plan.buys[i].minTokenOut);
|
|
100
|
-
}
|
|
101
|
-
const replay = await simulateLaunchPlan({ client, planned, limits, fork });
|
|
102
|
-
assert.equal(replay.admitted, true, replay.reason);
|
|
91
|
+
if (mode === "staged" && planned.hookDeployments.length > 0) planned = await planLaunch({ client, account: plan.creator, plan, mode, limits });
|
|
92
|
+
assert.equal(planned.assessment.ready, true, planned.assessment.reason);
|
|
103
93
|
const receipts = [];
|
|
104
94
|
let activationReceipt;
|
|
105
95
|
for (let guard = 0; guard < 64; ++guard) {
|
|
106
|
-
const next = await buildNextTransaction({ client, planned, receipts, limits
|
|
96
|
+
const next = await buildNextTransaction({ client, planned, receipts, limits });
|
|
107
97
|
if (!next) break;
|
|
108
|
-
assert.equal(next.
|
|
98
|
+
assert.equal(next.assessment?.ready, true);
|
|
109
99
|
const submitted = await send(next);
|
|
110
100
|
receipts.push(submitted.reference);
|
|
111
101
|
if (["atomic", "activate"].includes(next.kind)) activationReceipt = submitted.receipt;
|
|
@@ -130,7 +120,7 @@ for (const row of exported.plans) for (const mode of ["atomic", "staged"]) {
|
|
|
130
120
|
assert.equal(liquidity, position.liquidity); assert.equal(custody.toLowerCase(), market.prepared.custody.toLowerCase());
|
|
131
121
|
}
|
|
132
122
|
}
|
|
133
|
-
assert.equal(await buildNextTransaction({ client, planned, receipts, limits
|
|
123
|
+
assert.equal(await buildNextTransaction({ client, planned, receipts, limits }), undefined);
|
|
134
124
|
const hub = progress.canonical.feeHub;
|
|
135
125
|
assert.equal(await readContract(hub, lifecycleFeeHubAbi, "economicVersion"), 3);
|
|
136
126
|
const authors = new Map();
|