@black-market/sdk 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +28 -4
  2. package/dist/addresses.d.ts +1 -1
  3. package/dist/addresses.js +4 -4
  4. package/dist/launch.d.ts +61 -0
  5. package/dist/launch.d.ts.map +1 -1
  6. package/dist/launch.js +171 -6
  7. package/dist/launch.js.map +1 -1
  8. package/dist/lifecycle/abi.d.ts +1999 -253
  9. package/dist/lifecycle/abi.d.ts.map +1 -1
  10. package/dist/lifecycle/abi.js +43 -14
  11. package/dist/lifecycle/abi.js.map +1 -1
  12. package/dist/lifecycle/allocations.d.ts +11 -0
  13. package/dist/lifecycle/allocations.d.ts.map +1 -0
  14. package/dist/lifecycle/allocations.js +36 -0
  15. package/dist/lifecycle/allocations.js.map +1 -0
  16. package/dist/lifecycle/calibration.d.ts +19 -0
  17. package/dist/lifecycle/calibration.d.ts.map +1 -0
  18. package/dist/lifecycle/calibration.js +65 -0
  19. package/dist/lifecycle/calibration.js.map +1 -0
  20. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts +11 -0
  21. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.d.ts.map +1 -0
  22. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js +10 -0
  23. package/dist/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.js.map +1 -0
  24. package/dist/lifecycle/index.d.ts +4 -2
  25. package/dist/lifecycle/index.d.ts.map +1 -1
  26. package/dist/lifecycle/index.js +4 -2
  27. package/dist/lifecycle/index.js.map +1 -1
  28. package/dist/lifecycle/markets.d.ts +170 -6
  29. package/dist/lifecycle/markets.d.ts.map +1 -1
  30. package/dist/lifecycle/markets.js +164 -37
  31. package/dist/lifecycle/markets.js.map +1 -1
  32. package/dist/lifecycle/planner.d.ts +7 -14
  33. package/dist/lifecycle/planner.d.ts.map +1 -1
  34. package/dist/lifecycle/planner.js +355 -392
  35. package/dist/lifecycle/planner.js.map +1 -1
  36. package/dist/lifecycle/presets.d.ts +62 -0
  37. package/dist/lifecycle/presets.d.ts.map +1 -0
  38. package/dist/lifecycle/presets.js +195 -0
  39. package/dist/lifecycle/presets.js.map +1 -0
  40. package/dist/lifecycle/progress.d.ts +5 -1
  41. package/dist/lifecycle/progress.d.ts.map +1 -1
  42. package/dist/lifecycle/progress.js +286 -118
  43. package/dist/lifecycle/progress.js.map +1 -1
  44. package/dist/lifecycle/rpc.d.ts +13 -2
  45. package/dist/lifecycle/rpc.d.ts.map +1 -1
  46. package/dist/lifecycle/rpc.js +92 -8
  47. package/dist/lifecycle/rpc.js.map +1 -1
  48. package/dist/lifecycle/simulation.d.ts +14 -1
  49. package/dist/lifecycle/simulation.d.ts.map +1 -1
  50. package/dist/lifecycle/simulation.js +400 -100
  51. package/dist/lifecycle/simulation.js.map +1 -1
  52. package/dist/lifecycle/transactions.d.ts +6 -0
  53. package/dist/lifecycle/transactions.d.ts.map +1 -0
  54. package/dist/lifecycle/transactions.js +40 -0
  55. package/dist/lifecycle/transactions.js.map +1 -0
  56. package/dist/lifecycle/types.d.ts +39 -2
  57. package/dist/lifecycle/types.d.ts.map +1 -1
  58. package/dist/lifecycle/types.js.map +1 -1
  59. package/docs/launch-lifecycle.md +236 -39
  60. package/examples/README.md +108 -18
  61. package/examples/generate-lifecycle-commitment-fixture.mjs +3 -1
  62. package/examples/launch-example-support.mjs +1 -1
  63. package/examples/launch-example.mjs +73 -26
  64. package/examples/launch-lifecycle-smoke.mjs +13 -11
  65. package/examples/launch.mjs +12 -11
  66. package/examples/quickstart.mjs +1 -1
  67. package/package.json +1 -1
  68. package/src/addresses.ts +4 -4
  69. package/src/launch.ts +213 -6
  70. package/src/lifecycle/abi.ts +43 -14
  71. package/src/lifecycle/allocations.ts +35 -0
  72. package/src/lifecycle/calibration.ts +59 -0
  73. package/src/lifecycle/deployments/fixed-fee-pool-hook-v1-20261008.ts +11 -0
  74. package/src/lifecycle/index.ts +4 -2
  75. package/src/lifecycle/markets.ts +169 -35
  76. package/src/lifecycle/planner.ts +326 -321
  77. package/src/lifecycle/presets.ts +243 -0
  78. package/src/lifecycle/progress.ts +254 -102
  79. package/src/lifecycle/rpc.ts +97 -9
  80. package/src/lifecycle/simulation.ts +391 -94
  81. package/src/lifecycle/transactions.ts +34 -0
  82. package/src/lifecycle/types.ts +35 -3
@@ -17,6 +17,8 @@ class InvocationLifecycleClient implements LifecycleRpcClient {
17
17
  private closed = false;
18
18
  private readonly concurrency: number;
19
19
  private diagnosticSequence = 0;
20
+ private chainId?: Promise<bigint>;
21
+ private readonly limitObservations = new Map<LifecycleLimitSource | undefined, Map<string, Promise<LifecycleLimitObservation>>>();
20
22
  constructor(readonly source: LifecycleRpcClient) {
21
23
  this.concurrency = source.supportsReadBatching === true ? lifecycleReadConcurrency : 1;
22
24
  }
@@ -28,7 +30,7 @@ class InvocationLifecycleClient implements LifecycleRpcClient {
28
30
  return result;
29
31
  }
30
32
 
31
- private assertOpen(): void {
33
+ assertOpen(): void {
32
34
  if (this.closed) throw new LifecyclePlanningError("READ_SCOPE_CLOSED", "Lifecycle read invocation has already settled");
33
35
  }
34
36
 
@@ -59,6 +61,34 @@ class InvocationLifecycleClient implements LifecycleRpcClient {
59
61
  }
60
62
  }
61
63
 
64
+ observeChainId(): Promise<bigint> {
65
+ this.assertOpen();
66
+ return this.chainId ??= this.run("eth_chainId", []).then((value) => rpcQuantity(value, "chain ID"));
67
+ }
68
+
69
+ observeLimits(context: LifecycleLimitContext, source?: LifecycleLimitSource): Promise<LifecycleLimitObservation> {
70
+ this.assertOpen();
71
+ // A caller callback is live policy, not an immutable pinned observation.
72
+ // Its current result must validate provenance; native fixed-state reads still reuse their wire cache.
73
+ if (typeof source === "function") return resolveLifecycleLimitsFresh(context, source).then((result) => {
74
+ this.assertOpen();
75
+ result.assertCurrent();
76
+ return result;
77
+ });
78
+ const key = JSON.stringify([context.chainId.toString(), context.account.toLowerCase(), context.orchestrator.toLowerCase(), context.block.number.toString(), context.block.hash.toLowerCase(), context.block.timestamp.toString(), context.block.gasLimit.toString(), context.block.baseFeePerGas?.toString()]);
79
+ let byContext = this.limitObservations.get(source);
80
+ if (byContext === undefined) { byContext = new Map(); this.limitObservations.set(source, byContext); }
81
+ const contexts = byContext;
82
+ let observation = byContext.get(key);
83
+ if (observation === undefined) {
84
+ observation = resolveLifecycleLimitsFresh(context, source);
85
+ byContext.set(key, observation);
86
+ const owned = observation;
87
+ void owned.catch(() => { if (contexts.get(key) === owned) contexts.delete(key); });
88
+ }
89
+ return observation.then((result) => { this.assertOpen(); result.assertCurrent(); return result; });
90
+ }
91
+
62
92
  read(method: string, params: readonly unknown[], block: LifecycleBlock): Promise<unknown> {
63
93
  this.assertOpen();
64
94
  // Source identity is this scope's original client. The complete wire call includes
@@ -79,6 +109,8 @@ class InvocationLifecycleClient implements LifecycleRpcClient {
79
109
  close(): void {
80
110
  this.closed = true;
81
111
  this.reads.clear();
112
+ this.chainId = undefined;
113
+ this.limitObservations.clear();
82
114
  }
83
115
  }
84
116
 
@@ -94,6 +126,13 @@ export function lifecycleSourceClient(client: LifecycleRpcClient): LifecycleRpcC
94
126
  return client instanceof InvocationLifecycleClient ? client.source : client;
95
127
  }
96
128
 
129
+ /** Internal continuations cannot retain proof after their read owner settles. */
130
+ export function assertLifecycleReadClientOpen(client: LifecycleRpcClient): boolean {
131
+ if (!(client instanceof InvocationLifecycleClient)) return false;
132
+ client.assertOpen();
133
+ return true;
134
+ }
135
+
97
136
  function emitLifecycleDiagnostic(listener: LifecycleDiagnosticListener, event: LifecycleDiagnosticEvent): void {
98
137
  try {
99
138
  const pending = listener(event);
@@ -129,6 +168,13 @@ export async function lifecyclePinnedRpc(client: LifecycleRpcClient, method: "et
129
168
  export async function lifecycleRpc(client: LifecycleRpcClient, method: string, params: readonly unknown[] = []): Promise<unknown> {
130
169
  return client instanceof InvocationLifecycleClient ? client.run(method, params) : client.request({ method, params });
131
170
  }
171
+ /** One invocation's observed identity. End-of-operation and submission checks
172
+ * deliberately use uncached eth_chainId so a switched provider cannot retain proof. */
173
+ export function readLifecycleChainId(client: LifecycleRpcClient): Promise<bigint> {
174
+ return client instanceof InvocationLifecycleClient
175
+ ? client.observeChainId()
176
+ : lifecycleRpc(client, "eth_chainId").then((value) => rpcQuantity(value, "chain ID"));
177
+ }
132
178
  export function rpcObject(value: unknown, label: string): Record<string, unknown> {
133
179
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", `${label} must be an object`);
134
180
  // The object guard establishes the RPC record boundary; fields remain untrusted unknowns.
@@ -144,6 +190,19 @@ export function rpcQuantity(value: unknown, label: string): bigint {
144
190
  if (!/^0x[0-9a-fA-F]+$/.test(hex)) throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", `${label} must be a quantity`);
145
191
  return BigInt(hex);
146
192
  }
193
+
194
+ /** Public refusal details contain only fixed text and numeric RPC/HTTP codes, never provider messages. */
195
+ export function lifecycleFailureReason(failure: unknown): string {
196
+ for (let depth = 0; depth < 8 && failure !== null && typeof failure === "object"; depth += 1) {
197
+ const error = failure as { code?: unknown; status?: unknown; statusCode?: unknown; cause?: unknown };
198
+ const status = error.status ?? error.statusCode;
199
+ if (typeof status === "number" && Number.isSafeInteger(status) && status >= 400 && status <= 599) return `HTTP read failed (status ${status})`;
200
+ if (typeof error.code === "number" && Number.isSafeInteger(error.code)) return `RPC request failed (code ${error.code})`;
201
+ if (failure instanceof LifecyclePlanningError) return "Lifecycle planning guard rejected the request";
202
+ failure = error.cause;
203
+ }
204
+ return "Read or execution proof failed";
205
+ }
147
206
  export async function readLifecycleBlock(client: LifecycleRpcClient, tag: Hex | "latest" = "latest"): Promise<LifecycleBlock> {
148
207
  const value = rpcObject(await lifecycleRpc(client, "eth_getBlockByNumber", [tag, false]), "block");
149
208
  const hash = rpcHex(value.hash, "block hash");
@@ -205,12 +264,37 @@ async function readNitroScalar(context: LifecycleLimitContext, to: Address, abi:
205
264
  return decodeFunctionResult({ abi, functionName, data: result }) as bigint;
206
265
  }
207
266
 
208
- export async function resolveLifecycleLimits(context: LifecycleLimitContext, source?: LifecycleLimitSource): Promise<{ limits: ResolvedLifecycleLimits; configuration: LifecycleLimits }> {
267
+ export type LifecycleLimitObservation = {
268
+ limits: ResolvedLifecycleLimits;
269
+ configuration: LifecycleLimits;
270
+ assertCurrent: () => void;
271
+ };
272
+
273
+ /** Reuse only SDK-owned, pinned observations inside the still-open invocation.
274
+ * Caller-supplied policies remain checked for mutation on every consumption. */
275
+ export function resolveLifecycleLimits(context: LifecycleLimitContext, source?: LifecycleLimitSource): Promise<LifecycleLimitObservation> {
276
+ return context.client instanceof InvocationLifecycleClient
277
+ ? context.client.observeLimits(context, source)
278
+ : resolveLifecycleLimitsFresh(context, source);
279
+ }
280
+
281
+ async function resolveLifecycleLimitsFresh(context: LifecycleLimitContext, source?: LifecycleLimitSource): Promise<LifecycleLimitObservation> {
209
282
  let configuration: LifecycleLimits;
210
283
  const policyContext = typeof source === "function" && context.client instanceof InvocationLifecycleClient ? { ...context, client: context.client.source } : context;
211
284
  try { configuration = source === undefined ? {} : typeof source === "function" ? await source(policyContext) : source; }
212
- catch (failure) { throw new LifecyclePlanningError("LIMIT_SOURCE_FAILED", `Supplied execution policy could not be resolved: ${failure instanceof Error ? failure.message : String(failure)}`); }
285
+ catch (failure) {
286
+ if (failure instanceof Error && failure.name === "AbortError") throw failure;
287
+ throw new LifecyclePlanningError("LIMIT_SOURCE_FAILED", `Supplied execution policy could not be resolved: ${lifecycleFailureReason(failure)}`);
288
+ }
213
289
  if (configuration === null || typeof configuration !== "object" || Array.isArray(configuration)) throw new LifecyclePlanningError("INVALID_LIMITS", "Supplied execution policy must be an object");
290
+ const suppliedConfiguration = configuration;
291
+ configuration = Object.freeze({ ...configuration });
292
+ const configurationKeys = Object.keys(configuration) as (keyof LifecycleLimits)[];
293
+ const assertCurrent = () => {
294
+ if (Object.keys(suppliedConfiguration).length !== configurationKeys.length || configurationKeys.some((key) => suppliedConfiguration[key] !== configuration[key])) {
295
+ throw new LifecyclePlanningError("LIMIT_CONTEXT_MISMATCH", "Supplied execution policy changed after its pinned observation");
296
+ }
297
+ };
214
298
  const headroomBps = configuration.headroomBps === undefined ? 1500 : configuration.headroomBps;
215
299
  if (!Number.isSafeInteger(headroomBps) || headroomBps < 0 || headroomBps > 10000) throw new LifecyclePlanningError("INVALID_LIMITS", "Headroom must be between zero and 10000 basis points");
216
300
  for (const limit of [context.block.gasLimit, configuration.chainGasLimit, configuration.rpcGasLimit, configuration.accountGasLimit, configuration.maxSimulationGas]) {
@@ -240,7 +324,10 @@ export async function resolveLifecycleLimits(context: LifecycleLimitContext, sou
240
324
  maxTxComputeGas = txComputeGas;
241
325
  maxBlockComputeGas = blockComputeGas;
242
326
  if ([maxTxComputeGas, maxBlockComputeGas].some((limit) => typeof limit !== "bigint" || limit <= 0n || limit > maxUint64)) throw new Error("Nitro compute getters must be positive uint64 values");
243
- } catch (failure) { throw new LifecyclePlanningError("NITRO_LIMITS_UNAVAILABLE", `Pinned Nitro compute limits could not be established: ${failure instanceof Error ? failure.message : String(failure)}; header gasLimit is not a fallback`); }
327
+ } catch (failure) {
328
+ if (failure instanceof Error && failure.name === "AbortError") throw failure;
329
+ throw new LifecyclePlanningError("NITRO_LIMITS_UNAVAILABLE", `Pinned Nitro compute limits could not be established: ${lifecycleFailureReason(failure)}; header gasLimit is not a fallback`);
330
+ }
244
331
  if (configuration.estimateDataFee !== undefined) throw new LifecyclePlanningError("INVALID_LIMITS", "Nitro poster fees are already included in gas; an external data-fee estimator would double-charge them");
245
332
  }
246
333
  const envelopeCaps = [configuration.chainGasLimit, configuration.rpcGasLimit, configuration.accountGasLimit].filter((limit): limit is bigint => limit !== undefined);
@@ -256,15 +343,16 @@ export async function resolveLifecycleLimits(context: LifecycleLimitContext, sou
256
343
  ...(configuration.maxCalldataBytes === undefined ? ["Applicable chain/account/RPC calldata byte cap is unknown"] : []),
257
344
  ...(configuration.maxSimulationGas === undefined ? ["RPC aggregate simulation gas cap is unknown"] : []),
258
345
  ];
346
+ assertCurrent();
259
347
  return {
260
- configuration,
261
- limits: {
348
+ configuration, assertCurrent,
349
+ limits: Object.freeze({
262
350
  protocol, executionGasCeiling, transactionGasCeiling, arbOSVersion, maxTxComputeGas, maxBlockComputeGas, headroomBps,
263
351
  maxCalldataBytes: configuration.maxCalldataBytes, maxSimulationGas: configuration.maxSimulationGas,
264
352
  blockGasLimit: context.block.gasLimit, chainGasLimit: configuration.chainGasLimit,
265
353
  rpcGasLimit: configuration.rpcGasLimit, accountGasLimit: configuration.accountGasLimit,
266
- unknownExecutionConstraints,
267
- unknownConstraints: [...unknownExecutionConstraints, ...(protocol === "evm" && configuration.estimateDataFee === undefined ? ["External chain-specific data fee is unavailable"] : [])],
268
- },
354
+ unknownExecutionConstraints: Object.freeze(unknownExecutionConstraints),
355
+ unknownConstraints: Object.freeze([...unknownExecutionConstraints, ...(protocol === "evm" && configuration.estimateDataFee === undefined ? ["External chain-specific data fee is unavailable"] : [])]),
356
+ }),
269
357
  };
270
358
  }