@black-market/sdk 0.10.0 → 0.12.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 (83) hide show
  1. package/README.md +21 -2
  2. package/dist/lifecycle/abi.d.ts +1 -0
  3. package/dist/lifecycle/abi.d.ts.map +1 -1
  4. package/dist/lifecycle/abi.js +1 -0
  5. package/dist/lifecycle/abi.js.map +1 -1
  6. package/dist/lifecycle/calibration.d.ts.map +1 -1
  7. package/dist/lifecycle/calibration.js +2 -0
  8. package/dist/lifecycle/calibration.js.map +1 -1
  9. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts +71 -0
  10. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts.map +1 -0
  11. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js +73 -0
  12. package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js.map +1 -0
  13. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts +71 -0
  14. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts.map +1 -0
  15. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js +73 -0
  16. package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js.map +1 -0
  17. package/dist/lifecycle/index.d.ts +1 -0
  18. package/dist/lifecycle/index.d.ts.map +1 -1
  19. package/dist/lifecycle/index.js +1 -0
  20. package/dist/lifecycle/index.js.map +1 -1
  21. package/dist/lifecycle/markets.d.ts +3 -1
  22. package/dist/lifecycle/markets.d.ts.map +1 -1
  23. package/dist/lifecycle/markets.js +20 -3
  24. package/dist/lifecycle/markets.js.map +1 -1
  25. package/dist/lifecycle/planner.d.ts.map +1 -1
  26. package/dist/lifecycle/planner.js +83 -54
  27. package/dist/lifecycle/planner.js.map +1 -1
  28. package/dist/lifecycle/presets.d.ts +9 -2
  29. package/dist/lifecycle/presets.d.ts.map +1 -1
  30. package/dist/lifecycle/presets.js +26 -5
  31. package/dist/lifecycle/presets.js.map +1 -1
  32. package/dist/lifecycle/progress.d.ts.map +1 -1
  33. package/dist/lifecycle/progress.js +26 -5
  34. package/dist/lifecycle/progress.js.map +1 -1
  35. package/dist/lifecycle/rpc.js +3 -2
  36. package/dist/lifecycle/rpc.js.map +1 -1
  37. package/dist/lifecycle/simulation.d.ts +1 -1
  38. package/dist/lifecycle/simulation.d.ts.map +1 -1
  39. package/dist/lifecycle/simulation.js +119 -175
  40. package/dist/lifecycle/simulation.js.map +1 -1
  41. package/dist/lifecycle/transactions.d.ts +6 -0
  42. package/dist/lifecycle/transactions.d.ts.map +1 -1
  43. package/dist/lifecycle/transactions.js +39 -0
  44. package/dist/lifecycle/transactions.js.map +1 -1
  45. package/dist/lifecycle/types.d.ts +17 -4
  46. package/dist/lifecycle/types.d.ts.map +1 -1
  47. package/dist/lifecycle/types.js.map +1 -1
  48. package/dist/lifecycle/ufg-abi.d.ts +2386 -0
  49. package/dist/lifecycle/ufg-abi.d.ts.map +1 -0
  50. package/dist/lifecycle/ufg-abi.js +171 -0
  51. package/dist/lifecycle/ufg-abi.js.map +1 -0
  52. package/dist/lifecycle/ufg-config.d.ts +38 -0
  53. package/dist/lifecycle/ufg-config.d.ts.map +1 -0
  54. package/dist/lifecycle/ufg-config.js +75 -0
  55. package/dist/lifecycle/ufg-config.js.map +1 -0
  56. package/dist/lifecycle/ufg.d.ts +21 -0
  57. package/dist/lifecycle/ufg.d.ts.map +1 -0
  58. package/dist/lifecycle/ufg.js +98 -0
  59. package/dist/lifecycle/ufg.js.map +1 -0
  60. package/docs/launch-lifecycle.md +215 -32
  61. package/examples/.env.example +4 -0
  62. package/examples/README.md +35 -2
  63. package/examples/launch-example-support.ts +1 -0
  64. package/examples/launch-example.ts +1 -0
  65. package/examples/launch.ts +1 -0
  66. package/examples/ufg-launch-config.ts +31 -0
  67. package/package.json +1 -1
  68. package/src/lifecycle/abi.ts +1 -0
  69. package/src/lifecycle/calibration.ts +1 -0
  70. package/src/lifecycle/deployments/ufg-dynamic-pool-20261021.ts +74 -0
  71. package/src/lifecycle/deployments/ufg-fixed-pool-20261021.ts +74 -0
  72. package/src/lifecycle/index.ts +1 -0
  73. package/src/lifecycle/markets.ts +16 -3
  74. package/src/lifecycle/planner.ts +75 -51
  75. package/src/lifecycle/presets.ts +27 -6
  76. package/src/lifecycle/progress.ts +22 -5
  77. package/src/lifecycle/rpc.ts +3 -2
  78. package/src/lifecycle/simulation.ts +114 -166
  79. package/src/lifecycle/transactions.ts +38 -1
  80. package/src/lifecycle/types.ts +15 -5
  81. package/src/lifecycle/ufg-abi.ts +173 -0
  82. package/src/lifecycle/ufg-config.ts +77 -0
  83. package/src/lifecycle/ufg.ts +86 -0
@@ -1 +1 @@
1
- {"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/lifecycle/simulation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwD,KAAK,cAAc,EAAoF,KAAK,mBAAmB,EAAgC,KAAK,oBAAoB,EAAoD,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAyS9V,qFAAqF;AACrF,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAClF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,YAAY,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC,GAAG;IACjK,4FAA4F;IAC5F,OAAO,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACrF,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAQF;wFACwF;AACxF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,UAAO,GAAG,wBAAwB,CAsHnJ;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAOtO"}
1
+ {"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/lifecycle/simulation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwD,KAAK,cAAc,EAAoF,KAAK,mBAAmB,EAAgC,KAAK,oBAAoB,EAAoD,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AA0S9V,qFAAqF;AACrF,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAClF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,YAAY,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC,GAAG;IACjK,4FAA4F;IAC5F,OAAO,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACrF,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAQF;wFACwF;AACxF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,UAAO,EAAE,gBAAgB,CAAC,EAAE,uBAAuB,GAAG,wBAAwB,CAuH/L;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAOtO"}
@@ -4,6 +4,9 @@ import { hashLaunchIdentity, hashLaunchPlan, LifecyclePhase } from "./schema.js"
4
4
  import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleRpc, lifecycleSourceClient, lifecycleStage, nitroArbSys, nitroArbSysAbi, nitroGasInfo, nitroGasInfoAbi, readLifecycleBlock, readLifecycleChainId, readNitroPosterGas, resolveLifecycleLimits, rpcHex, rpcObject, rpcQuantity } from "./rpc.js";
5
5
  import { LifecyclePlanningError } from "./types.js";
6
6
  import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
7
+ import { hmmV2ForwardingReserve } from "./presets.js";
8
+ import { transactionGasEstimate } from "./transactions.js";
9
+ import { hashUfgLaunchConfig } from "./ufg-config.js";
7
10
  function committedPositionCount(plan) {
8
11
  let count = 0;
9
12
  for (const market of plan.markets) {
@@ -119,9 +122,6 @@ async function proveNitroBackend(client, planned, block, limits, gasPrice) {
119
122
  // (including every refusal path); a separate probe-only recheck is redundant.
120
123
  }
121
124
  async function simulateRpcPass(client, planned, block, transactions, gasLimits, limits, gasPrice, validation) {
122
- const totalRequestedGas = gasLimits.reduce((total, gas) => total + gas, 0n);
123
- if (limits.maxSimulationGas !== undefined && totalRequestedGas > limits.maxSimulationGas)
124
- throw new LifecyclePlanningError("SIMULATION_RPC_CAP", "Sequential request exceeds the current RPC aggregate simulation gas cap; use an isolated controlled fork");
125
125
  // Natural child-block fees must govern fee-bearing admission; overriding
126
126
  // baseFeePerGas with the parent's would fabricate a lower envelope and admit
127
127
  // calls that the actual chain would reject. eth_simulateV1 derives the child
@@ -184,7 +184,7 @@ async function simulateRpcPass(client, planned, block, transactions, gasLimits,
184
184
  throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not satisfy every ordered buy's budget and output minimum");
185
185
  }
186
186
  }
187
- else if (transaction.kind === "cancel" && !events.some((event) => event.eventName === "LaunchCancelled" && event.args.launchId.toLowerCase() === planned.launchId.toLowerCase() && event.args.creator.toLowerCase() === planned.account.toLowerCase()))
187
+ else if (transaction.kind === "cancel" && !events.some((event) => event.eventName === "LaunchCancelled" && event.args.launchId.toLowerCase() === planned.launchId.toLowerCase() && event.args.creator.toLowerCase() === planned.plan.creator.toLowerCase()))
188
188
  throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not cancel the exact committed launch");
189
189
  else if ((transaction.kind === "approve" || transaction.kind === "approve-reset") && returnData !== "0x" && decodeFunctionResult({ abi: lifecycleErc20Abi, functionName: "approve", data: returnData }) !== true)
190
190
  throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Funding approval returned failure");
@@ -358,7 +358,7 @@ function observedFields(value) {
358
358
  }
359
359
  /** Internal continuation owned by one SDK read invocation. Only its positive
360
360
  * capability proof is reused; every candidate executes its entire economic sequence. */
361
- export function prepareLaunchSimulation(options, block, hasTransactions = true) {
361
+ export function prepareLaunchSimulation(options, block, hasTransactions = true, reviewedEnvelope) {
362
362
  const { client, planned, fork } = options;
363
363
  const signal = options.signal;
364
364
  if (signal?.aborted)
@@ -392,6 +392,8 @@ export function prepareLaunchSimulation(options, block, hasTransactions = true)
392
392
  if (hashLaunchPlan(plan).toLowerCase() !== planHash.toLowerCase() || hashLaunchIdentity(plan).toLowerCase() !== launchId.toLowerCase()) {
393
393
  throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed after its simulation context was bound");
394
394
  }
395
+ if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash)
396
+ throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed after its simulation context was bound");
395
397
  };
396
398
  const pending = (async () => {
397
399
  if (fork !== undefined && lifecycleSourceClient(fork.client) === sourceClient)
@@ -400,7 +402,7 @@ export function prepareLaunchSimulation(options, block, hasTransactions = true)
400
402
  resolveLifecycleLimits(context, policy),
401
403
  readLifecycleChainId(client),
402
404
  lifecyclePinnedRpc(client, "eth_getCode", [context.account, toHex(context.block.number)], context.block),
403
- hasTransactions ? lifecycleRpc(client, "eth_gasPrice") : undefined,
405
+ hasTransactions && reviewedEnvelope === undefined ? lifecycleRpc(client, "eth_gasPrice") : undefined,
404
406
  ]));
405
407
  })().then((observations) => ({ observations }), (failure) => ({ failure }));
406
408
  // Refused inputs may discard this read-only preparation before it settles.
@@ -491,7 +493,7 @@ export async function simulateLaunchTransactions(options, transactions, pinnedBl
491
493
  if (options.fork !== undefined && lifecycleSourceClient(options.fork.client) === lifecycleSourceClient(options.client))
492
494
  throw new LifecyclePlanningError("UNSAFE_FORK", "Source RPC must never receive controlled simulation transactions/snapshots/resets");
493
495
  const block = pinnedBlock ?? await readLifecycleBlock(options.client);
494
- const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0);
496
+ const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0, nextEnvelope);
495
497
  try {
496
498
  return await prepared(transactions, nextEnvelope);
497
499
  }
@@ -504,7 +506,7 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
504
506
  const sourceClient = lifecycleSourceClient(client);
505
507
  const { block } = context;
506
508
  const [{ limits, configuration }, chainId, accountCodeValue, gasPriceValue] = observations;
507
- const base = { confidence: "stateful", executionProof: "failed", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account: planned.account, chainId: planned.chainId, limits };
509
+ const base = { confidence: "stateful", executionProof: "failed", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account: planned.account, chainId: planned.chainId, limits, deferredTransactionIds: [] };
508
510
  if (chainId !== planned.chainId)
509
511
  throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain changed before sequential simulation");
510
512
  const accountCode = rpcHex(accountCodeValue, "account code");
@@ -524,38 +526,50 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
524
526
  await assertLifecycleBlock(client, block, planned.chainId);
525
527
  return { ...base, executionProof: "proved", protocolFit: "proved", backend: "unavailable", admitted: true, steps: [] };
526
528
  }
527
- const recommendedGasPrice = rpcQuantity(gasPriceValue, "gas price");
528
- if (recommendedGasPrice >= 1n << 256n)
529
- throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Recommended gas price must be a uint256 quantity");
530
- // Initial construction reserves twice the pinned base fee if the quote lags.
531
- // Reviewed fees are not new recommendations: native replay must accept them
532
- // under natural child fees, with no base-fee override or silent fee increase.
529
+ if (nextEnvelope !== undefined && (transactions.length !== 1 || typeof nextEnvelope.gas !== "bigint" || nextEnvelope.gas <= 0n || nextEnvelope.gas >= 1n << 64n ||
530
+ typeof nextEnvelope.gasPrice !== "bigint" || nextEnvelope.gasPrice < 0n || nextEnvelope.gasPrice >= 1n << 256n)) {
531
+ throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Reviewed replay requires one immediate transaction with exact uint64 gas and uint256 price");
532
+ }
533
+ const allTransactions = transactions;
534
+ if (limits.maxSimulationGas !== undefined && nextEnvelope === undefined) {
535
+ let estimatedGas = 0n, count = 0;
536
+ for (const transaction of transactions) {
537
+ estimatedGas += transactionGasEstimate(planned.plan, transaction);
538
+ if (estimatedGas > limits.maxSimulationGas)
539
+ break;
540
+ count += 1;
541
+ }
542
+ transactions = transactions.slice(0, count);
543
+ base.deferredTransactionIds = allTransactions.slice(count).map((transaction) => transaction.id);
544
+ if (count === 0) {
545
+ const computeCap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
546
+ const exceedsCompute = estimatedGas > computeCap;
547
+ const gasInMillions = (gas) => {
548
+ const tenths = (gas + 50000n) / 100000n;
549
+ return `${tenths / 10n}.${tenths % 10n}`;
550
+ };
551
+ const atomic = allTransactions[0].kind === "atomic";
552
+ const reason = exceedsCompute
553
+ ? `${atomic ? "Atomic launch" : "Immediate transaction"} needs about ${gasInMillions(estimatedGas)}M gas, above the ${gasInMillions(computeCap)}M per-transaction limit${atomic ? "; use staged" : ""}`
554
+ : "The immediate transaction estimate exceeds the RPC aggregate simulation gas cap; no prefix can be proved";
555
+ await assertLifecycleBlock(client, block, planned.chainId);
556
+ return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], failureCategory: "capacity", capacityConstraint: exceedsCompute ? "compute" : "simulation-gas", reason };
557
+ }
558
+ }
559
+ const recommendedGasPrice = nextEnvelope === undefined ? rpcQuantity(gasPriceValue, "gas price") : nextEnvelope.gasPrice;
560
+ // New recommendations cover lagging quotes; reviewed fees are replayed unchanged
561
+ // under natural child fees, never refreshed or silently increased.
533
562
  const baseFeeEnvelope = (block.baseFeePerGas ?? 0n) * 2n;
534
563
  const gasPrice = nextEnvelope?.gasPrice ?? (recommendedGasPrice > baseFeeEnvelope ? recommendedGasPrice : baseFeeEnvelope);
535
564
  if (gasPrice >= 1n << 256n)
536
565
  throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Gas price must be a uint256 quantity");
537
- if (nextEnvelope !== undefined && (typeof nextEnvelope.gas !== "bigint" || nextEnvelope.gas <= 0n || nextEnvelope.gas >= 1n << 64n ||
538
- typeof nextEnvelope.gasPrice !== "bigint" || nextEnvelope.gasPrice < 0n || nextEnvelope.gasPrice >= 1n << 256n)) {
539
- throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Reviewed gas and gas price must be exact positive uint64 gas and nonnegative uint256 price");
540
- }
541
566
  const buffer = (gas) => (gas * BigInt(10000 + limits.headroomBps) + 9999n) / 10000n;
542
- const computeCeiling = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
543
- // Nonnegative poster gas cannot make this complete discovery request smaller.
544
- // A generic controlled fork retains the existing RPC-to-fork fallback.
545
- if (limits.maxSimulationGas !== undefined && BigInt(transactions.length) * computeCeiling > limits.maxSimulationGas &&
546
- (limits.protocol === "nitro" || options.fork === undefined)) {
547
- assertCurrent();
548
- await assertLifecycleBlock(client, block, planned.chainId);
549
- return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], failureCategory: "capacity", capacityConstraint: "simulation-gas", reason: `Stateful sequential RPC simulation unavailable: Sequential request exceeds the current RPC aggregate simulation gas cap; use an isolated controlled fork. ${limits.protocol === "nitro" ? "Generic Anvil/Hardhat forks cannot prove ArbOS compute/poster metering." : "Dependent eth_call estimates are not proof."}` };
550
- }
551
567
  const posterGas = [];
552
568
  if (limits.protocol === "nitro") {
553
- // Lightweight poster reads may overlap the isolated capability request.
554
- // Economic work is not even queued until both prerequisites have succeeded.
555
569
  try {
556
570
  const [, estimates] = await Promise.all([
557
571
  proveBackend(limits, gasPrice),
558
- lifecycleStage(client, "simulation.poster", () => Promise.all(transactions.map(async (transaction) => buffer((await readNitroPosterGas(transaction, context)).posterGas)))),
572
+ nextEnvelope === undefined ? lifecycleStage(client, "simulation.poster", () => Promise.all(transactions.map(async (transaction) => buffer((await readNitroPosterGas(transaction, context)).posterGas)))) : [],
559
573
  ]);
560
574
  posterGas.push(...estimates);
561
575
  }
@@ -568,8 +582,6 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
568
582
  }
569
583
  }
570
584
  assertCurrent();
571
- // The poster quote allocates gas only. The native exact replay enforces actual
572
- // compute hold; subtracting this approximate budget from gasUsed would not.
573
585
  const discoveryGas = transactions.map((_, index) => {
574
586
  const total = limits.executionGasCeiling + (posterGas[index] ?? 0n);
575
587
  const capped = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < total ? limits.transactionGasCeiling : total;
@@ -577,116 +589,96 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
577
589
  throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Compute/poster envelope exceeds uint64 gas");
578
590
  return capped;
579
591
  });
580
- let backend = "eth_simulateV1";
581
- let measured;
582
- // A permissive measurement needs the legal complete compute + poster envelope.
583
- // It is not proof: fixed compute/headroom checks and exact validated replay still follow.
584
- try {
585
- measured = await lifecycleStage(client, "simulation.measure", () => simulateRpcPass(client, planned, block, transactions, discoveryGas, limits, gasPrice, false));
586
- }
587
- catch (failure) {
588
- if (simulationFailureKind(failure) === "abort")
589
- throw failure;
592
+ let backend = nextEnvelope !== undefined && limits.protocol === "evm" && options.fork !== undefined ? "controlled-fork" : "eth_simulateV1";
593
+ let measured = [];
594
+ if (nextEnvelope === undefined) {
595
+ try {
596
+ measured = await lifecycleStage(client, "simulation.measure", () => simulateRpcPass(client, planned, block, transactions, discoveryGas, limits, gasPrice, false));
597
+ }
598
+ catch (failure) {
599
+ if (simulationFailureKind(failure) === "abort")
600
+ throw failure;
601
+ assertCurrent();
602
+ if (options.fork === undefined || limits.protocol === "nitro") {
603
+ await assertLifecycleBlock(client, block, planned.chainId);
604
+ return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], reason: `Stateful sequential RPC simulation unavailable: ${lifecycleFailureReason(failure)}. ${limits.protocol === "nitro" ? "Generic Anvil/Hardhat forks cannot prove ArbOS compute/poster metering." : "Dependent eth_call estimates are not proof."}` };
605
+ }
606
+ backend = "controlled-fork";
607
+ measured = await simulateForkPass(options.fork, planned, block, transactions, discoveryGas, gasPrice, true, limits.headroomBps);
608
+ }
590
609
  assertCurrent();
591
- if (options.fork === undefined || limits.protocol === "nitro") {
610
+ const failedIndex = measured.findIndex((step) => !step.success);
611
+ if (failedIndex >= 0 && limits.maxSimulationGas !== undefined &&
612
+ measured.reduce((gas, step) => gas + (step.gasUsed ?? 0n), 0n) * 100n >= limits.maxSimulationGas * 95n) {
613
+ // The provider exhausted its aggregate used-gas budget, not the contract.
614
+ // Preserve only actual successful results; the failed call is deferred too.
615
+ measured = measured.slice(0, failedIndex);
616
+ transactions = transactions.slice(0, failedIndex);
617
+ base.deferredTransactionIds = allTransactions.slice(failedIndex).map((transaction) => transaction.id);
618
+ if (transactions.length === 0) {
619
+ await assertLifecycleBlock(client, block, planned.chainId);
620
+ return { ...base, confidence: "provisional", executionProof: "unavailable", backend, admitted: false, steps: [], failureCategory: "source", reason: "RPC aggregate simulation gas cap left no successful prefix to prove" };
621
+ }
622
+ }
623
+ const failed = measured.find((step) => !step.success);
624
+ if (failed !== undefined) {
592
625
  await assertLifecycleBlock(client, block, planned.chainId);
593
- return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], reason: `Stateful sequential RPC simulation unavailable: ${lifecycleFailureReason(failure)}. ${limits.protocol === "nitro" ? "Generic Anvil/Hardhat forks cannot prove ArbOS compute/poster metering." : "Dependent eth_call estimates are not proof."}` };
626
+ return { ...base, backend, admitted: false, steps: measured, reason: failed.error, failedTransactionId: failed.transactionId, failureCategory: failed.failureCategory ?? "opaque", capacityConstraint: failed.failureCategory === "capacity" ? "compute" : undefined };
627
+ }
628
+ if (diagnostic) {
629
+ await assertLifecycleBlock(client, block, planned.chainId);
630
+ return { ...base, backend, admitted: false, executionProof: "unavailable", steps: measured };
594
631
  }
595
- backend = "controlled-fork";
596
- measured = await simulateForkPass(options.fork, planned, block, transactions, discoveryGas, gasPrice, true, limits.headroomBps);
597
- }
598
- assertCurrent();
599
- const failed = measured.find((step) => !step.success);
600
- if (failed !== undefined) {
601
- await assertLifecycleBlock(client, block, planned.chainId);
602
- return { ...base, backend, admitted: false, steps: measured, reason: failed.error, failedTransactionId: failed.transactionId, failureCategory: failed.failureCategory ?? "opaque", capacityConstraint: failed.failureCategory === "capacity" ? "compute" : undefined };
603
- }
604
- if (diagnostic) {
605
- await assertLifecycleBlock(client, block, planned.chainId);
606
- return { ...base, backend, admitted: false, executionProof: "unavailable", steps: measured };
607
632
  }
608
- // Padding belongs to new envelopes. A reviewed envelope may consume its
609
- // original margin; only exact replay at its held gas/price proves it still fits.
610
- const computeGas = measured.map((step, index) => {
611
- const required = step.gasRequired ?? step.gasUsed ?? computeCeiling;
612
- return nextEnvelope !== undefined && index === 0 ? required : buffer(required);
613
- });
614
- const gasLimits = computeGas.map((gas, index) => nextEnvelope !== undefined && index === 0 ? nextEnvelope.gas : gas + (posterGas[index] ?? 0n));
615
- const exceeds = gasLimits.findIndex((gas, index) => gas <= 0n || gas >= 1n << 64n || (computeGas[index] ?? 0n) > limits.executionGasCeiling || limits.transactionGasCeiling !== undefined && gas > limits.transactionGasCeiling);
633
+ const gasLimits = nextEnvelope === undefined ? measured.map((step, index) => {
634
+ const gross = step.gasRequired ?? step.gasUsed;
635
+ const reserve = transactions[index]?.kind === "activate" || transactions[index]?.kind === "atomic" ? hmmV2ForwardingReserve(planned.plan) : 0n;
636
+ const desired = gross + reserve > buffer(gross) ? gross + reserve : buffer(gross);
637
+ const available = discoveryGas[index] - (posterGas[index] ?? 0n);
638
+ return (desired < available ? desired : available) + (posterGas[index] ?? 0n);
639
+ }) : [nextEnvelope.gas];
640
+ const exceeds = gasLimits.findIndex((gas, index) => gas <= 0n || gas >= 1n << 64n || limits.transactionGasCeiling !== undefined && gas > limits.transactionGasCeiling ||
641
+ nextEnvelope === undefined && (measured[index].gasRequired ?? measured[index].gasUsed) > gas - (posterGas[index] ?? 0n) ||
642
+ limits.protocol === "evm" && gas > limits.executionGasCeiling);
616
643
  if (exceeds >= 0) {
617
644
  await assertLifecycleBlock(client, block, planned.chainId);
618
- return { ...base, executionProof: "unavailable", protocolFit: "failed", backend, admitted: false, steps: measured, failureCategory: "capacity", capacityConstraint: (computeGas[exceeds] ?? 0n) > limits.executionGasCeiling ? "compute" : "gas-envelope", reason: "Execution compute/headroom or complete poster gas envelope is outside positive uint64 gas and current protocol/known transaction limits", failedTransactionId: transactions[exceeds]?.id };
645
+ return { ...base, executionProof: "unavailable", protocolFit: "failed", backend, admitted: false, steps: measured, failureCategory: "capacity", capacityConstraint: "gas-envelope", reason: "Measured execution or reviewed gas exceeds the current compute/complete transaction envelope", failedTransactionId: transactions[exceeds]?.id };
619
646
  }
620
- // Envelope discovery may enlarge future unfinished steps, never the reviewed
621
- // immediate one. Every retry still executes the entire sequence at held price.
622
- const envelopeDiscoveryGas = nextEnvelope === undefined ? discoveryGas : discoveryGas.map((gas, index) => index === 0 ? nextEnvelope.gas : gas);
623
- // Replay the complete sequence from its original state at the exact buffered
624
- // limits. Only explicit native OOG can discover one larger legal envelope;
625
- // semantic, opaque and transport failures cannot authorize a capacity retry.
626
- let verified;
627
- let validatedReplayError;
628
- assertCurrent();
647
+ let verifiedSteps = [];
648
+ let admittedGasLimits = gasLimits;
649
+ let replayError;
650
+ const replay = (gas) => backend === "eth_simulateV1"
651
+ ? simulateRpcPass(client, planned, block, transactions, gas, limits, gasPrice, true)
652
+ : simulateForkPass(options.fork, planned, block, transactions, gas, gasPrice, false, limits.headroomBps);
629
653
  try {
630
- if (backend === "eth_simulateV1")
631
- verified = await lifecycleStage(client, "simulation.replay", () => simulateRpcPass(client, planned, block, transactions, gasLimits, limits, gasPrice, true));
632
- else {
633
- if (options.fork === undefined)
634
- throw new LifecyclePlanningError("INVALID_SIMULATION", "Controlled fork is missing for the exact-gas replay");
635
- verified = await simulateForkPass(options.fork, planned, block, transactions, gasLimits, gasPrice, false, limits.headroomBps);
636
- }
654
+ verifiedSteps = await lifecycleStage(client, "simulation.replay", () => replay(gasLimits));
637
655
  }
638
656
  catch (failure) {
639
657
  const kind = simulationFailureKind(failure);
640
658
  if (kind === "abort")
641
659
  throw failure;
642
660
  assertCurrent();
643
- const message = lifecycleFailureReason(failure);
644
- if (backend !== "eth_simulateV1") {
661
+ if (kind !== "out-of-gas" || backend !== "eth_simulateV1" || nextEnvelope !== undefined) {
645
662
  await assertLifecycleBlock(client, block, planned.chainId);
646
- return { ...base, backend, admitted: false, steps: [], reason: `Exact-gas validated replay failed: ${message}` };
663
+ return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Exact-gas validated replay unavailable: ${lifecycleFailureReason(failure)}` };
647
664
  }
648
- if (kind !== "out-of-gas") {
649
- await assertLifecycleBlock(client, block, planned.chainId);
650
- return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Exact-gas validated replay unavailable: ${message}` };
651
- }
652
- validatedReplayError = message;
653
- verified = [];
665
+ replayError = lifecycleFailureReason(failure);
654
666
  }
655
667
  assertCurrent();
656
- let verifiedSteps = verified;
657
- let admittedGasLimits = gasLimits;
658
668
  let finalFailure = verifiedSteps.find((step) => !step.success);
659
- let finalFailureReason;
660
- if (finalFailure === undefined && validatedReplayError !== undefined) {
661
- if (!envelopeDiscoveryGas.some((gas, index) => gas > (gasLimits[index] ?? gas))) {
662
- await assertLifecycleBlock(client, block, planned.chainId);
663
- return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling failed: ${validatedReplayError}` };
664
- }
665
- // The buffered validated replay threw before yielding steps. Attempt one
666
- // explicit envelope-discovery replay at the exact execution ceiling on the
667
- // same pinned block; headroom is never silently raised — the carried gas is
668
- // the validated envelope itself.
669
+ const failedIndex = verifiedSteps.findIndex((step) => !step.success);
670
+ const rescue = nextEnvelope === undefined && backend === "eth_simulateV1" &&
671
+ (replayError !== undefined && discoveryGas.some((gas, index) => index < transactions.length && gas > gasLimits[index]) ||
672
+ finalFailure !== undefined && looksGasExhausted(finalFailure, gasLimits[failedIndex]) && discoveryGas[failedIndex] > gasLimits[failedIndex]);
673
+ // One legal-envelope rescue covers thrown/returned genuine OOG. Never retry
674
+ // semantic errors, switch replay backends, or enlarge a held immediate envelope.
675
+ if (rescue) {
669
676
  assertCurrent();
670
677
  try {
671
- const ceilingLimits = envelopeDiscoveryGas;
672
- const retried = backend === "eth_simulateV1"
673
- ? await lifecycleStage(client, "simulation.envelope", () => simulateRpcPass(client, planned, block, transactions, ceilingLimits, limits, gasPrice, true))
674
- : options.fork === undefined ? undefined : await simulateForkPass(options.fork, planned, block, transactions, ceilingLimits, gasPrice, false, limits.headroomBps);
675
- const retryFailureIndex = retried?.findIndex((step) => !step.success) ?? -1;
676
- const retryFailure = retried?.[retryFailureIndex];
677
- if (retried === undefined) {
678
- await assertLifecycleBlock(client, block, planned.chainId);
679
- return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling still failed: ${validatedReplayError}` };
680
- }
681
- admittedGasLimits = ceilingLimits;
682
- verifiedSteps = retried;
683
- finalFailure = retryFailure;
684
- validatedReplayError = undefined;
685
- if (retryFailure !== undefined) {
686
- const kind = transactions[retryFailureIndex]?.kind;
687
- const indivisible = kind === "activate" || kind === "atomic";
688
- finalFailureReason = `Validated execution at the exact current gas ceiling still failed: ${retryFailure.error ?? "Execution reverted"}${indivisible ? "; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned" : ""}`;
689
- }
678
+ admittedGasLimits = discoveryGas.slice(0, transactions.length);
679
+ verifiedSteps = await lifecycleStage(client, "simulation.envelope", () => replay(admittedGasLimits));
680
+ finalFailure = verifiedSteps.find((step) => !step.success);
681
+ replayError = undefined;
690
682
  }
691
683
  catch (failure) {
692
684
  const kind = simulationFailureKind(failure);
@@ -695,63 +687,15 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
695
687
  assertCurrent();
696
688
  await assertLifecycleBlock(client, block, planned.chainId);
697
689
  if (kind !== "out-of-gas")
698
- return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Legal-envelope validated replay unavailable after buffered execution refusal: ${lifecycleFailureReason(failure)}` };
699
- return { ...base, backend, admitted: false, steps: measured, reason: `Validated buffered replay failed (${validatedReplayError}) and execution at the exact gas ceiling still reverted; final mint/lock/all-buys/public-opening is indivisible` };
690
+ return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Legal-envelope validated replay unavailable: ${lifecycleFailureReason(failure)}` };
691
+ return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling still failed: ${lifecycleFailureReason(failure)}` };
700
692
  }
701
693
  }
702
- else if (finalFailure !== undefined) {
703
- const failedIndex = verifiedSteps.findIndex((step) => !step.success);
704
- const bufferedLimit = gasLimits[failedIndex];
705
- const failedTransaction = transactions[failedIndex];
706
- if (bufferedLimit !== undefined && failedTransaction !== undefined && (nextEnvelope === undefined || failedIndex !== 0) &&
707
- looksGasExhausted(finalFailure, bufferedLimit) && (envelopeDiscoveryGas[failedIndex] ?? 0n) > bufferedLimit) {
708
- // Buffered replay returned an exhaustion-shaped failed step; learn the true
709
- // validated envelope with one explicit replay at the exact execution ceiling.
710
- assertCurrent();
711
- try {
712
- const ceilingLimits = envelopeDiscoveryGas;
713
- const retried = backend === "eth_simulateV1"
714
- ? await lifecycleStage(client, "simulation.envelope", () => simulateRpcPass(client, planned, block, transactions, ceilingLimits, limits, gasPrice, true))
715
- : options.fork === undefined ? undefined : await simulateForkPass(options.fork, planned, block, transactions, ceilingLimits, gasPrice, false, limits.headroomBps);
716
- if (retried === undefined) {
717
- await assertLifecycleBlock(client, block, planned.chainId);
718
- return { ...base, backend, admitted: false, steps: measured, reason: "Validated execution at the exact current gas ceiling omitted transaction results" };
719
- }
720
- const retryFailureIndex = retried.findIndex((step) => !step.success);
721
- const retryFailure = retried[retryFailureIndex];
722
- admittedGasLimits = ceilingLimits;
723
- verifiedSteps = retried;
724
- finalFailure = retryFailure;
725
- if (retryFailure !== undefined) {
726
- const kind = transactions[retryFailureIndex]?.kind;
727
- const indivisible = kind === "activate" || kind === "atomic";
728
- finalFailureReason = `Validated execution at the exact current gas ceiling still failed: ${retryFailure.error ?? "Execution reverted"}${indivisible ? "; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned" : ""}`;
729
- }
730
- }
731
- catch (failure) {
732
- const kind = simulationFailureKind(failure);
733
- if (kind === "abort")
734
- throw failure;
735
- assertCurrent();
736
- if (kind !== "out-of-gas") {
737
- await assertLifecycleBlock(client, block, planned.chainId);
738
- return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, reason: `Legal-envelope validated replay unavailable: ${lifecycleFailureReason(failure)}` };
739
- }
740
- await assertLifecycleBlock(client, block, planned.chainId);
741
- return { ...base, backend, admitted: false, steps: measured, reason: `Validated buffered replay failed (${finalFailure?.error ?? "Execution reverted"}) and execution at the exact gas ceiling still reverted: ${lifecycleFailureReason(failure)}` };
742
- }
743
- }
694
+ if (replayError !== undefined) {
695
+ await assertLifecycleBlock(client, block, planned.chainId);
696
+ return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling failed: ${replayError}` };
744
697
  }
745
698
  assertCurrent();
746
- if (nextEnvelope !== undefined && finalFailure === undefined) {
747
- const currentMinimum = verifiedSteps[0]?.gasRequired ?? verifiedSteps[0]?.gasUsed ?? nextEnvelope.gas;
748
- if (nextEnvelope.gas < currentMinimum) {
749
- await assertLifecycleBlock(client, block, planned.chainId);
750
- return { ...base, backend, admitted: false, executionProof: "proved", protocolFit: "failed", steps: verifiedSteps,
751
- failureCategory: "capacity", capacityConstraint: "gas-envelope", failedTransactionId: transactions[0]?.id,
752
- reason: "Exact reviewed replay exceeds the held gas envelope" };
753
- }
754
- }
755
699
  const estimates = [];
756
700
  // A definitive returned replay failure already refuses execution. Retain its
757
701
  // fee observations without inventing a balance or doing redundant payer work.
@@ -783,6 +727,6 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
783
727
  estimates.push({ ...step, estimate: { gasUsed: step.gasUsed ?? 0n, gasLimit, gasPrice, executionFee, dataFee, posterGas: poster, posterFee: poster === undefined ? undefined : poster * gasPrice, dataFeeIncludedInGas: limits.protocol === "nitro", totalFee: dataFee === undefined ? undefined : executionFee + dataFee, feeConfidence: dataFee === undefined ? "execution-only" : "execution-and-data" } });
784
728
  }
785
729
  await assertLifecycleBlock(client, block, planned.chainId);
786
- return { ...base, backend, executionProof: finalFailure === undefined ? "proved" : "failed", protocolFit: finalFailure === undefined ? "proved" : "unknown", admitted: finalFailure === undefined && affordabilityFailure === undefined, steps: estimates, reason: finalFailureReason ?? finalFailure?.error ?? affordabilityFailure, failedTransactionId: finalFailure?.transactionId, failureCategory: finalFailure?.failureCategory ?? (affordabilityFailure === undefined ? undefined : "affordability"), capacityConstraint: finalFailure?.failureCategory === "capacity" ? "compute" : undefined };
730
+ return { ...base, backend, executionProof: finalFailure === undefined ? "proved" : "failed", protocolFit: finalFailure === undefined ? "proved" : "unknown", admitted: finalFailure === undefined && affordabilityFailure === undefined, steps: estimates, reason: finalFailure?.error ?? affordabilityFailure, failedTransactionId: finalFailure?.transactionId, failureCategory: finalFailure?.failureCategory ?? (affordabilityFailure === undefined ? undefined : "affordability"), capacityConstraint: finalFailure?.failureCategory === "capacity" ? "compute" : undefined };
787
731
  }
788
732
  //# sourceMappingURL=simulation.js.map